diff --git a/2490.pdf b/2490.pdf
new file mode 100644
index 0000000..139099b
Binary files /dev/null and b/2490.pdf differ
diff --git a/2491.pdf b/2491.pdf
new file mode 100644
index 0000000..94c9b92
Binary files /dev/null and b/2491.pdf differ
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d81faab
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,27 @@
+Freeware License, some rights reserved
+
+Copyright (c) 2006 Matthew Moodie
+
+Permission is hereby granted, free of charge, to anyone obtaining a copy
+of this software and associated documentation files (the "Software"),
+to work with the Software within the limits of freeware distribution and fair use.
+This includes the rights to use, copy, and modify the Software for personal use.
+Users are also allowed and encouraged to submit corrections and modifications
+to the Software for the benefit of other users.
+
+It is not allowed to reuse, modify, or redistribute the Software for
+commercial use in any way, or for a user’s educational materials such as books
+or blog articles without prior permission from the copyright holder.
+
+The above copyright notice and this permission notice need to be included
+in all copies or substantial portions of the software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b2993ab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+#Apress Source Code
+
+This repository accompanies [*Pro Apache Ant*](http://www.apress.com/9781590595596) by Matthew Moodie (Apress, 2006).
+
+![Cover image](9781590595596.jpg)
+
+Download the files as a zip using the green button, or clone the repository to your machine using Git.
+
+##Releases
+
+Release v1.0 corresponds to the code in the published book, without corrections or updates.
+
+##Contributions
+
+See the file Contributing.md for more information on how you can contribute to this repository.
diff --git a/ch03/build.dtd.xml b/ch03/build.dtd.xml
new file mode 100644
index 0000000..952d9b5
--- /dev/null
+++ b/ch03/build.dtd.xml
@@ -0,0 +1,14 @@
+
+
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch05/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch05/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch05/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch05/src/shared/java/org/mwrm/plants/SelectData.java b/ch05/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch05/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch05/src/shared/java/org/mwrm/plants/package.html b/ch05/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch05/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch05/src/stand-alone/docs/index.html b/ch05/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch05/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch05/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch05/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch05/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch05/src/web/conf/antBook.xml b/ch05/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch05/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch05/src/web/java/org/mwrm/plants/servlets/package.html b/ch05/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch05/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch05/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch05/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch05/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch05/src/web/pages/footer.html b/ch05/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch05/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch05/src/web/pages/menu.jsp b/ch05/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch05/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch05/src/web/pages/template.jsp b/ch05/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch05/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch06/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch06/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch06/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch06/src/shared/java/org/mwrm/plants/SelectData.java b/ch06/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch06/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch06/src/shared/java/org/mwrm/plants/package.html b/ch06/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch06/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch06/src/stand-alone/docs/index.html b/ch06/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch06/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch06/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch06/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch06/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch06/src/web/conf/antBook.xml b/ch06/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch06/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch06/src/web/java/org/mwrm/plants/servlets/package.html b/ch06/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch06/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch06/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch06/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch06/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch06/src/web/pages/footer.html b/ch06/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch06/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch06/src/web/pages/menu.jsp b/ch06/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch06/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch06/src/web/pages/template.jsp b/ch06/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch06/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch07/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch07/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch07/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch07/src/shared/java/org/mwrm/plants/SelectData.java b/ch07/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch07/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch07/src/shared/java/org/mwrm/plants/package.html b/ch07/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch07/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch07/src/stand-alone/docs/index.html b/ch07/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch07/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch07/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch07/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch07/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch07/src/web/conf/antBook.xml b/ch07/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch07/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch07/src/web/java/org/mwrm/plants/servlets/package.html b/ch07/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch07/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch07/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch07/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch07/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch07/src/web/pages/footer.html b/ch07/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch07/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch07/src/web/pages/menu.jsp b/ch07/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch07/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch07/src/web/pages/template.jsp b/ch07/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch07/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch08/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch08/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch08/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch08/src/shared/java/org/mwrm/plants/SelectData.java b/ch08/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch08/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch08/src/shared/java/org/mwrm/plants/package.html b/ch08/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch08/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch08/src/stand-alone/docs/index.html b/ch08/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch08/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch08/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch08/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch08/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch08/src/web/conf/antBook.xml b/ch08/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch08/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch08/src/web/java/org/mwrm/plants/servlets/package.html b/ch08/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch08/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch08/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch08/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch08/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch08/src/web/pages/footer.html b/ch08/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch08/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch08/src/web/pages/menu.jsp b/ch08/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch08/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch08/src/web/pages/template.jsp b/ch08/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch08/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
Tests the plant servlet. + * It checks that the web application is running + * and then checks the session is emptied + * if the there are no results in the query.
+ */ +public class PlantWebTest extends TestCase { + + /** The URL of the server. */ + private static final String SERVER_URL = "http://localhost:8080"; + + /** The web application's name. */ + private static final String WEB_APP = "/antBook"; + + /** The response code we're looking for. */ + private static final int RESPONSE_CODE = 200; + + /** + *The constructor,
+ * which simply calls super(name)
.
We want to make sure that the web application is running.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testIsRunning() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + try { + // Send a request to the web application's root + WebResponse resp = wc.getResponse(SERVER_URL + WEB_APP); + + // If there is a 200 return code, then it is available + assertEquals("Web application not available at " + + SERVER_URL + WEB_APP, + RESPONSE_CODE, resp.getResponseCode()); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } + + /** + *The application should detect that no results have been obtained.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testSession() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + + try { + // First send a request that will not produce any results + WebResponse resp = + wc.getResponse(SERVER_URL + WEB_APP + + "/plants/listPlants.jsp?show=name&letter=X"); + // Check that this is the case + assertTrue("Session not cancelled after empty results", + (resp.getText().indexOf("Sorry") > -1)); + + // Now go to the index page, + // where there should not be an error message + resp = wc.getResponse(SERVER_URL + WEB_APP + "/plants/"); + assertTrue("Session not cancelled after empty results", + !(resp.getText().indexOf("Sorry") > -1)); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } +} diff --git a/ch08/test/org/mwrm/plants/package.html b/ch08/test/org/mwrm/plants/package.html new file mode 100644 index 0000000..a9c3f06 --- /dev/null +++ b/ch08/test/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Contains the test classes for the plant application.
+ + diff --git a/ch09/build.jstl.xml b/ch09/build.jstl.xml new file mode 100644 index 0000000..491976d --- /dev/null +++ b/ch09/build.jstl.xml @@ -0,0 +1,44 @@ + + +
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch09/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch09/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch09/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch09/src/shared/java/org/mwrm/plants/SelectData.java b/ch09/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch09/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch09/src/shared/java/org/mwrm/plants/package.html b/ch09/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch09/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch09/src/stand-alone/docs/index.html b/ch09/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch09/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch09/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch09/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch09/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch09/src/web/conf/antBook.xml b/ch09/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch09/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch09/src/web/java/org/mwrm/plants/servlets/package.html b/ch09/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch09/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch09/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch09/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch09/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch09/src/web/pages/footer.html b/ch09/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch09/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch09/src/web/pages/menu.jsp b/ch09/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch09/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch09/src/web/pages/template.jsp b/ch09/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch09/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
Tests the plant servlet. + * It checks that the web application is running + * and then checks the session is emptied + * if the there are no results in the query.
+ */ +public class PlantWebTest extends TestCase { + + /** The URL of the server. */ + private static final String SERVER_URL = "http://localhost:8080"; + + /** The web application's name. */ + private static final String WEB_APP = "/antBook"; + + /** The response code we're looking for. */ + private static final int RESPONSE_CODE = 200; + + /** + *The constructor,
+ * which simply calls super(name)
.
We want to make sure that the web application is running.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testIsRunning() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + try { + // Send a request to the web application's root + WebResponse resp = wc.getResponse(SERVER_URL + WEB_APP); + + // If there is a 200 return code, then it is available + assertEquals("Web application not available at " + + SERVER_URL + WEB_APP, + RESPONSE_CODE, resp.getResponseCode()); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } + + /** + *The application should detect that no results have been obtained.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testSession() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + + try { + // First send a request that will not produce any results + WebResponse resp = + wc.getResponse(SERVER_URL + WEB_APP + + "/plants/listPlants.jsp?show=name&letter=X"); + // Check that this is the case + assertTrue("Session not cancelled after empty results", + (resp.getText().indexOf("Sorry") > -1)); + + // Now go to the index page, + // where there should not be an error message + resp = wc.getResponse(SERVER_URL + WEB_APP + "/plants/"); + assertTrue("Session not cancelled after empty results", + !(resp.getText().indexOf("Sorry") > -1)); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } +} diff --git a/ch09/test/org/mwrm/plants/package.html b/ch09/test/org/mwrm/plants/package.html new file mode 100644 index 0000000..a9c3f06 --- /dev/null +++ b/ch09/test/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Contains the test classes for the plant application.
+ + diff --git a/ch10/ant/org/mwrm/ant/tasks/ClassSetTask.java b/ch10/ant/org/mwrm/ant/tasks/ClassSetTask.java new file mode 100644 index 0000000..5afbdfd --- /dev/null +++ b/ch10/ant/org/mwrm/ant/tasks/ClassSetTask.java @@ -0,0 +1,60 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.tasks; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.BuildException; + +/** + *The ClassSetTask
class
+ * demonstrates how to use a Class
argument
+ * in a custom class attribute.
Runs the task and displays the qualified name of the class
+ * that is set as the setQualifiedName
attribute.
Sets the fully qualified name of the class.
+ ** @param qName The fully qualified name of a class + */ + public final void setQualifiedName(final Class qName) { + if (qName.getName().equals("java.lang.Integer") + || + qName.getName().equals("java.lang.String")) { + log(qName.getName() + " found.", Project.MSG_INFO); + } else { + String msg = "You can only specify java.lang.Integer " + + "or java.lang.String in qualifiedName."; + throw new BuildException(msg); + } + this.qualifiedName = qName; + } +} + diff --git a/ch10/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java b/ch10/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java new file mode 100644 index 0000000..e9c076c --- /dev/null +++ b/ch10/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java @@ -0,0 +1,188 @@ +/* + * Extends org.apache.tools.ant.taskdefs.Javadoc + * and uses org.apache.tools.ant.taskdefs.UpToDate, + * which are Copyright 2000-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.ant.tasks; + +import java.io.File; + +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildException; + +import org.apache.tools.ant.taskdefs.Javadoc; +import org.apache.tools.ant.taskdefs.UpToDate; + +import org.apache.tools.ant.types.FileSet; + +/** + *The ExtendJavadocTask
class
+ * extends the {@link org.apache.tools.ant.taskdefs.Javadoc Javadoc} task.
+ * It checks whether a set of source files are newer than a set of target files
+ * and if so, it generates Javadocs.
Creates a new instance of an internal
+ * <uptodate>
task
+ * and adds it to the current project.
Checks if Javadocs should be created
+ * and then calls super.execute()
if so.
This method does usage checks on the task's attributes
+ * and its nested elements.
+ * It will throw a BuildException
if there is a violation.
Checks whether the files are up to date.
+ * @param file The file to evaluate + * @return boolean The result + */ + private boolean getResult(final File file) { + // Set the target property in theThe setter method for the target
attribute.
The setter method for the file set
+ * contained in the nested <srcfiles>
element.
The setter method for the file sets
+ * contained in nested <targetfiles>
elements.
At each stage in a task's life cycle, this class displays information
+ * to show the internal state of the task and its position with in the project.
+ * It takes a name
attribute.
This class demonstrates how to nest elements within a custom task.
+ * Its nested element is called <name>
+ * and cannot be used in conjunction with a name
attribute.
The constructor displays the state of the task + * as it is instantiated.
+ */ + public LifeCycleNestedTask() { + System.out.println("---------------"); + System.out.println("Constructor called"); + System.out.println("Value of name attribute: " + this.name); + System.out.println("Value of the body text: " + text); + System.out.println("Project: " + getProject()); + System.out.println("Location: " + getLocation()); + System.out.println("Target: " + getOwningTarget()); + System.out.println("---------------"); + } + + /** + *Displays the state of the task at initialization.
+ * @see #logAll(String method) + */ + public final void init() { + logAll("init()"); + } + + /** + *Displays the state of the task when Ant runs it. + * This method also runs some usage checks + * to ensure the task is being used properly.
+ */ + public final void execute() { + if (name != null && nameElements.size() > 0) { + String msg = "You can't specify a name attribute " + + "andSets the name to display + * and shows the state of the task afterwards.
+ * @param aName The name to display + */ + public final void setName(final String aName) { + // The value of the name attribute + this.name = aName; + logAll("setName()"); + } + + /** + *Sets the body text of the task + * and shows the state of the task afterwards.
+ * @param bodyText The body text + */ + public final void addText(final String bodyText) { + // If the body text is just whitespace, it might as well be null + if (bodyText.trim().equals("")) { + this.text = null; + } else { + this.text = bodyText.trim(); + } + logAll("addText()"); + } + + /**Checks for task references.
+ * @return String + * A string that tells us details of the reference check + */ + private String referenceCheck() { + + // The default setting + String setString = "Reference not found."; + + // We need the references that have been set in this project + Hashtable refs = getProject().getReferences(); + Enumeration e = refs.elements(); + + // Let's iterate over them + while (e.hasMoreElements()) { + // We want to work with each object, so we'll instantiate an object + Object obj = e.nextElement(); + + // Check to see if this object is a custom task + // If it is, we'll build a string that contains its name and type + if (obj.getClass().getName(). + equals("org.apache.tools.ant.UnknownElement") + || + obj.getClass().getName(). + equals(this.getClass().getName())) { + Task aTask = (Task) obj; + setString = + "Reference to " + aTask.getTaskName() + " found, of type " + + aTask.getClass().getName() + ". "; + setString = setString + "Its id is " + + aTask.getRuntimeConfigurableWrapper(). + getAttributeMap().get("id") + "."; + } + } + return setString; + } + + /** + *A central logging method that all the life-cycle methods call
+ * to display the state of the task.
+ * It displays the value of the name
attribute
+ * and other information about the task,
+ * including the name of its project and its location in the build file.
Adds a <name>
element
+ * that has not been initialized.
NameElement
+ * object that represents the nested element
+ * @see LifeCycleNestedTask.NameElement
+ */
+ public final void addName(final NameElement nameElement) {
+ nameElements.add(nameElement);
+
+ logAll("addName()");
+ log("Value of this name: "
+ + nameElement.getName(), Project.MSG_VERBOSE);
+ }
+
+ /**
+ * Adds a <name>
element
+ * that has been initialized.
NameElement
+ * object that represents the nested element
+ * @see LifeCycleNestedTask.NameElement
+ */
+ public final void addConfiguredName(final NameElement nameElement) {
+ nameElements.add(nameElement);
+
+ logAll("addConfiguredName()");
+ log("Value of this name: " + nameElement.getName(),
+ Project.MSG_VERBOSE);
+ }
+
+ /**
+ * Adds a <name>
element
+ * that has not been initialized.
+ * In this case, the createName()
method
+ * has the responsibility
+ * for creating the object.
A class that implements
+ * the nested <name>
element
+ * of a LifeCycleNestedTask
.
+ * @see LifeCycleNestedTask
+ */
+ public static class NameElement {
+
+ /** The name
attribute of this element. */
+ private String name;
+
+ /** Tells the class if we've used the overridden constructor. */
+ private boolean usedConstructor = false;
+
+ /** The empty constructor. */
+ public NameElement() {
+ // Empty
+ }
+
+ /**
+ *
Used by the LifeCycleNestedTask.createName()
method
+ * to created a nested <name>
element.
The mutator method for the name
attribute.
The accessor method for the name
attribute.
Sets the body text of the <name>
element.
+ * It contains a usage check.
At each stage in a task's life cycle, this class displays information
+ * to show the internal state of the task and its position with in the project.
+ * It takes a name
attribute.
name
attribute of this task. */
+ private String name;
+
+ /** The body text of this task. */
+ private String text;
+
+ /**
+ * The constructor displays the state of the task + * as it is instantiated.
+ */ + public LifeCycleTask() { + System.out.println("---------------"); + System.out.println("Constructor called"); + System.out.println("Value of name attribute: " + name); + System.out.println("Value of the body text: " + text); + System.out.println("Project: " + getProject()); + System.out.println("Location: " + getLocation()); + System.out.println("Target: " + getOwningTarget()); + System.out.println("---------------"); + } + + /** + *Displays the state of the task at initialization.
+ * @see #logAll(String method) + */ + public final void init() { + logAll("init()"); + } + + /** + *Displays the state of the task when Ant runs it. + * This method also runs some usage checks + * to ensure the task is being used properly.
+ */ + public final void execute() { + if (name == null) { + throw new BuildException("You must specify a name attribute in " + + getTaskName() + "."); + } + logAll("execute()"); + + // Write the name to output + log(name, Project.MSG_INFO); + } + + /** + *Sets the name to display + * and shows the state of the task afterwards.
+ * @param aName The name to display + */ + public final void setName(final String aName) { + // The value of the name attribute + this.name = aName; + logAll("setName()"); + } + + /** + *Sets the body text of the task + * and shows the state of the task afterwards.
+ * @param bodyText The body text + */ + public final void addText(final String bodyText) { + // If the body text is just whitespace, it might as well be null + if (bodyText.trim().equals("")) { + this.text = null; + } else { + this.text = bodyText.trim(); + } + logAll("addText()"); + } + + /**Checks for task references.
+ * @return String + * A string that tells us details of the reference check + */ + private String referenceCheck() { + + // The default setting + String setString = "Reference not found."; + + // We need the references that have been set in this project + Hashtable refs = getProject().getReferences(); + Enumeration e = refs.elements(); + + // Let's iterate over them + while (e.hasMoreElements()) { + // We want to work with each object, so we'll instantiate an object + Object obj = e.nextElement(); + + // Check to see whether this object is a task + // If it is, we'll build a string that contains its name and type + if (obj.getClass().getName(). + equals("org.apache.tools.ant.UnknownElement") + || + obj.getClass().getName(). + equals(this.getClass().getName())) { + + Task aTask = (Task) obj; + + setString = + "Reference to " + aTask.getTaskName() + " found, of type " + + aTask.getClass().getName() + ". "; + setString = setString + "Its id is " + + aTask.getRuntimeConfigurableWrapper(). + getAttributeMap().get("id") + "."; + } + } + return setString; + } + + /** + *A central logging method that all the life-cycle methods call
+ * to display the state of the task.
+ * It displays the value of the name
attribute
+ * and other information about the task,
+ * including the name of its project and its location in the build file.
The ProjectHelpTask
class displays usage information
+ * for the current project. This is the same information as is displayed
+ * by -projecthelp
.
Runs the task.
+ * It calls {@link org.apache.tools.ant.Main#main(String[] args)
+ * org.apache.tools.ant.Main.main()} with the -projecthelp
+ * parameter. It will also send the current build file's file name
+ * via the -f
parameter.
The buildfile
attribute is optional.
+ * The default is the task's build file.
buildfile
attribute.
+ * @param file The file name of the build file to use.
+ *
+ */
+ public final void setBuildfile(final String file) {
+ this.buildfile = file;
+ }
+
+}
+
diff --git a/ch10/ant/org/mwrm/ant/tasks/package.html b/ch10/ant/org/mwrm/ant/tasks/package.html
new file mode 100644
index 0000000..26003dc
--- /dev/null
+++ b/ch10/ant/org/mwrm/ant/tasks/package.html
@@ -0,0 +1,8 @@
+
+
+ A collection of classes that demonstrate the Ant task life cycle.
+ + diff --git a/ch10/antBook.antlib.xml b/ch10/antBook.antlib.xml new file mode 100644 index 0000000..7927073 --- /dev/null +++ b/ch10/antBook.antlib.xml @@ -0,0 +1,39 @@ + +
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch10/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch10/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch10/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch10/src/shared/java/org/mwrm/plants/SelectData.java b/ch10/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch10/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch10/src/shared/java/org/mwrm/plants/package.html b/ch10/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch10/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch10/src/stand-alone/docs/index.html b/ch10/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch10/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch10/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch10/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch10/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch10/src/web/conf/antBook.xml b/ch10/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch10/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch10/src/web/java/org/mwrm/plants/servlets/package.html b/ch10/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch10/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch10/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch10/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch10/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch10/src/web/pages/footer.html b/ch10/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch10/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch10/src/web/pages/menu.jsp b/ch10/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch10/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch10/src/web/pages/template.jsp b/ch10/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch10/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
Tests the plant servlet. + * It checks that the web application is running + * and then checks the session is emptied + * if the there are no results in the query.
+ */ +public class PlantWebTest extends TestCase { + + /** The URL of the server. */ + private static final String SERVER_URL = "http://localhost:8080"; + + /** The web application's name. */ + private static final String WEB_APP = "/antBook"; + + /** The response code we're looking for. */ + private static final int RESPONSE_CODE = 200; + + /** + *The constructor,
+ * which simply calls super(name)
.
We want to make sure that the web application is running.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testIsRunning() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + try { + // Send a request to the web application's root + WebResponse resp = wc.getResponse(SERVER_URL + WEB_APP); + + // If there is a 200 return code, then it is available + assertEquals("Web application not available at " + + SERVER_URL + WEB_APP, + RESPONSE_CODE, resp.getResponseCode()); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } + + /** + *The application should detect that no results have been obtained.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testSession() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + + try { + // First send a request that will not produce any results + WebResponse resp = + wc.getResponse(SERVER_URL + WEB_APP + + "/plants/listPlants.jsp?show=name&letter=X"); + // Check that this is the case + assertTrue("Session not cancelled after empty results", + (resp.getText().indexOf("Sorry") > -1)); + + // Now go to the index page, + // where there should not be an error message + resp = wc.getResponse(SERVER_URL + WEB_APP + "/plants/"); + assertTrue("Session not cancelled after empty results", + !(resp.getText().indexOf("Sorry") > -1)); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } +} diff --git a/ch10/test/org/mwrm/plants/package.html b/ch10/test/org/mwrm/plants/package.html new file mode 100644 index 0000000..a9c3f06 --- /dev/null +++ b/ch10/test/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Contains the test classes for the plant application.
+ + diff --git a/ch11/ant/org/mwrm/ant/listeners/BuildEventListener.java b/ch11/ant/org/mwrm/ant/listeners/BuildEventListener.java new file mode 100644 index 0000000..15f3dd1 --- /dev/null +++ b/ch11/ant/org/mwrm/ant/listeners/BuildEventListener.java @@ -0,0 +1,116 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.listeners; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.BuildEvent; + +/** + *A class that demonstrates some of the functionality + * of a custom listener.
+ */ +public class BuildEventListener implements BuildListener { + + /** + *Signals that a build has started. This event + * is fired before any targets have started.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ */
+ public final void buildStarted(final BuildEvent start) {
+ start.getProject().log("buildStarted() called.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that the last target has finished. This event + * will still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public final void buildFinished(final BuildEvent finish) {
+ finish.getProject().log("buildFinished() called.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a target is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTarget()
+ */
+ public final void targetStarted(final BuildEvent start) {
+ start.getProject().log("Target [" + start.getTarget().getName()
+ + "] started.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a target has finished. This event will + * still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public final void targetFinished(final BuildEvent finish) {
+ finish.getProject().log("Target [" + finish.getTarget().getName()
+ + "] finished.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a task is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTask()
+ */
+ public final void taskStarted(final BuildEvent start) {
+ start.getProject().log("Task [" + start.getTask().getTaskName()
+ + "] started.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a task has finished. This event will still + * be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public final void taskFinished(final BuildEvent finish) {
+ finish.getProject().log("Task [" + finish.getTask().getTaskName()
+ + "] finished.", Project.MSG_ERR);
+ }
+
+ /** When a message is sent to this logger, Ant calls this method.
+ * @param event An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public void messageLogged(final BuildEvent event) {
+ // empty
+ }
+}
diff --git a/ch11/ant/org/mwrm/ant/listeners/package.html b/ch11/ant/org/mwrm/ant/listeners/package.html
new file mode 100644
index 0000000..c12041d
--- /dev/null
+++ b/ch11/ant/org/mwrm/ant/listeners/package.html
@@ -0,0 +1,8 @@
+
+
+ A package that contains custom listeners.
+ + diff --git a/ch11/ant/org/mwrm/ant/loggers/BuildEventLogger.java b/ch11/ant/org/mwrm/ant/loggers/BuildEventLogger.java new file mode 100644 index 0000000..60eb52c --- /dev/null +++ b/ch11/ant/org/mwrm/ant/loggers/BuildEventLogger.java @@ -0,0 +1,195 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.loggers; + +import java.io.PrintStream; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.BuildEvent; + +/** + *A class that demonstrates some of the functionality + * of a custom logger.
+ */ +public class BuildEventLogger implements BuildLogger { + + /** + * PrintStream to write non-error messages to. + */ + private PrintStream out; + + /** + * PrintStream to write error messages to. + */ + private PrintStream err; + + /** + * Sets whether to tailor output for Emacs, etc. + * The default isfalse
.
+ */
+ private boolean emacsMode = false;
+
+ /**
+ * We'll set this logger to log only warnings.
+ */
+ private int msgOutputLevel = Project.MSG_WARN;
+
+ /**
+ * Signals that a build has started. This event + * is fired before any targets have started.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ */
+ public final void buildStarted(final BuildEvent start) {
+ start.getProject().log("Message from buildStarted().", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that the last target has finished. This event + * will still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void buildFinished(final BuildEvent finish) {
+ // empty
+ }
+
+ /**
+ * Signals that a target is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTarget()
+ */
+ public void targetStarted(final BuildEvent start) {
+ // empty
+ }
+
+ /**
+ * Signals that a target has finished. This event will + * still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void targetFinished(final BuildEvent finish) {
+ // empty
+ }
+
+ /**
+ * Signals that a task is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTask()
+ */
+ public void taskStarted(final BuildEvent start) {
+ // empty
+ }
+
+ /**
+ * Signals that a task has finished. This event will still + * be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void taskFinished(final BuildEvent finish) {
+ // empty
+ }
+
+ /** When a message is sent to this logger, Ant calls this method.
+ * @param event An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public final void messageLogged(final BuildEvent event) {
+ /// We need to determine how important this message is
+ int priority = event.getPriority();
+
+ // If it's as important as our log level, we display it
+ if (priority <= msgOutputLevel) {
+ out.println("messageLogged: " + event.getMessage());
+ }
+ }
+
+ /**
+ * Sets the output stream to which this logger is to send its output.
+ * + * @param output The output stream for the logger. + * Must not benull
.
+ */
+ public final void setOutputPrintStream(final PrintStream output) {
+ this.out = new PrintStream(output, true);
+ }
+
+ /**
+ * Sets the output stream to which this logger + * is to send error messages.
+ * + * @param errorStream The error stream for the logger. + * Must not benull
.
+ */
+ public final void setErrorPrintStream(final PrintStream errorStream) {
+ this.err = new PrintStream(errorStream, true);
+ }
+
+ /**
+ * Sets this logger to produce Emacs + * (and other editor) friendly output.
+ * + * @param modetrue
if output is to be unadorned so that
+ * Emacs and other editors can parse files names, etc.
+ */
+ public final void setEmacsMode(final boolean mode) {
+ this.emacsMode = mode;
+ }
+
+ /**
+ * Sets the highest level of message this logger should respond to.
+ * + *Only messages with a message level lower than or equal to the + * given level should be written to the log.
+ *
+ * Constants for the message levels are in the
+ * {@link Project Project} class. The order of the levels, from least
+ * to most verbose, is MSG_ERR
, MSG_WARN
,
+ * MSG_INFO
, MSG_VERBOSE
,
+ * MSG_DEBUG
.
The default for this logger is + * {@link Project#MSG_WARN Project.MSG_WARN}.
+ * + * @param level the logging level for the logger. + */ + public void setMessageOutputLevel(final int level) { + // We will leave this empty to use the default level, + // which we set above + } + +} diff --git a/ch11/ant/org/mwrm/ant/loggers/package.html b/ch11/ant/org/mwrm/ant/loggers/package.html new file mode 100644 index 0000000..e30bac9 --- /dev/null +++ b/ch11/ant/org/mwrm/ant/loggers/package.html @@ -0,0 +1,8 @@ + + +A package that contains custom loggers.
+ + diff --git a/ch11/ant/org/mwrm/ant/tasks/ClassSetTask.java b/ch11/ant/org/mwrm/ant/tasks/ClassSetTask.java new file mode 100644 index 0000000..5afbdfd --- /dev/null +++ b/ch11/ant/org/mwrm/ant/tasks/ClassSetTask.java @@ -0,0 +1,60 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.tasks; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.BuildException; + +/** + *The ClassSetTask
class
+ * demonstrates how to use a Class
argument
+ * in a custom class attribute.
Runs the task and displays the qualified name of the class
+ * that is set as the setQualifiedName
attribute.
Sets the fully qualified name of the class.
+ ** @param qName The fully qualified name of a class + */ + public final void setQualifiedName(final Class qName) { + if (qName.getName().equals("java.lang.Integer") + || + qName.getName().equals("java.lang.String")) { + log(qName.getName() + " found.", Project.MSG_INFO); + } else { + String msg = "You can only specify java.lang.Integer " + + "or java.lang.String in qualifiedName."; + throw new BuildException(msg); + } + this.qualifiedName = qName; + } +} + diff --git a/ch11/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java b/ch11/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java new file mode 100644 index 0000000..e9c076c --- /dev/null +++ b/ch11/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java @@ -0,0 +1,188 @@ +/* + * Extends org.apache.tools.ant.taskdefs.Javadoc + * and uses org.apache.tools.ant.taskdefs.UpToDate, + * which are Copyright 2000-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.ant.tasks; + +import java.io.File; + +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildException; + +import org.apache.tools.ant.taskdefs.Javadoc; +import org.apache.tools.ant.taskdefs.UpToDate; + +import org.apache.tools.ant.types.FileSet; + +/** + *The ExtendJavadocTask
class
+ * extends the {@link org.apache.tools.ant.taskdefs.Javadoc Javadoc} task.
+ * It checks whether a set of source files are newer than a set of target files
+ * and if so, it generates Javadocs.
Creates a new instance of an internal
+ * <uptodate>
task
+ * and adds it to the current project.
Checks if Javadocs should be created
+ * and then calls super.execute()
if so.
This method does usage checks on the task's attributes
+ * and its nested elements.
+ * It will throw a BuildException
if there is a violation.
Checks whether the files are up to date.
+ * @param file The file to evaluate + * @return boolean The result + */ + private boolean getResult(final File file) { + // Set the target property in theThe setter method for the target
attribute.
The setter method for the file set
+ * contained in the nested <srcfiles>
element.
The setter method for the file sets
+ * contained in nested <targetfiles>
elements.
At each stage in a task's life cycle, this class displays information
+ * to show the internal state of the task and its position with in the project.
+ * It takes a name
attribute.
This class demonstrates how to nest elements within a custom task.
+ * Its nested element is called <name>
+ * and cannot be used in conjunction with a name
attribute.
The constructor displays the state of the task + * as it is instantiated.
+ */ + public LifeCycleNestedTask() { + System.out.println("---------------"); + System.out.println("Constructor called"); + System.out.println("Value of name attribute: " + this.name); + System.out.println("Value of the body text: " + text); + System.out.println("Project: " + getProject()); + System.out.println("Location: " + getLocation()); + System.out.println("Target: " + getOwningTarget()); + System.out.println("---------------"); + } + + /** + *Displays the state of the task at initialization.
+ * @see #logAll(String method) + */ + public final void init() { + logAll("init()"); + } + + /** + *Displays the state of the task when Ant runs it. + * This method also runs some usage checks + * to ensure the task is being used properly.
+ */ + public final void execute() { + if (name != null && nameElements.size() > 0) { + String msg = "You can't specify a name attribute " + + "andSets the name to display + * and shows the state of the task afterwards.
+ * @param aName The name to display + */ + public final void setName(final String aName) { + // The value of the name attribute + this.name = aName; + logAll("setName()"); + } + + /** + *Sets the body text of the task + * and shows the state of the task afterwards.
+ * @param bodyText The body text + */ + public final void addText(final String bodyText) { + // If the body text is just whitespace, it might as well be null + if (bodyText.trim().equals("")) { + this.text = null; + } else { + this.text = bodyText.trim(); + } + logAll("addText()"); + } + + /**Checks for task references.
+ * @return String + * A string that tells us details of the reference check + */ + private String referenceCheck() { + + // The default setting + String setString = "Reference not found."; + + // We need the references that have been set in this project + Hashtable refs = getProject().getReferences(); + Enumeration e = refs.elements(); + + // Let's iterate over them + while (e.hasMoreElements()) { + // We want to work with each object, so we'll instantiate an object + Object obj = e.nextElement(); + + // Check to see if this object is a custom task + // If it is, we'll build a string that contains its name and type + if (obj.getClass().getName(). + equals("org.apache.tools.ant.UnknownElement") + || + obj.getClass().getName(). + equals(this.getClass().getName())) { + Task aTask = (Task) obj; + setString = + "Reference to " + aTask.getTaskName() + " found, of type " + + aTask.getClass().getName() + ". "; + setString = setString + "Its id is " + + aTask.getRuntimeConfigurableWrapper(). + getAttributeMap().get("id") + "."; + } + } + return setString; + } + + /** + *A central logging method that all the life-cycle methods call
+ * to display the state of the task.
+ * It displays the value of the name
attribute
+ * and other information about the task,
+ * including the name of its project and its location in the build file.
Adds a <name>
element
+ * that has not been initialized.
NameElement
+ * object that represents the nested element
+ * @see LifeCycleNestedTask.NameElement
+ */
+ public final void addName(final NameElement nameElement) {
+ nameElements.add(nameElement);
+
+ logAll("addName()");
+ log("Value of this name: "
+ + nameElement.getName(), Project.MSG_VERBOSE);
+ }
+
+ /**
+ * Adds a <name>
element
+ * that has been initialized.
NameElement
+ * object that represents the nested element
+ * @see LifeCycleNestedTask.NameElement
+ */
+ public final void addConfiguredName(final NameElement nameElement) {
+ nameElements.add(nameElement);
+
+ logAll("addConfiguredName()");
+ log("Value of this name: " + nameElement.getName(),
+ Project.MSG_VERBOSE);
+ }
+
+ /**
+ * Adds a <name>
element
+ * that has not been initialized.
+ * In this case, the createName()
method
+ * has the responsibility
+ * for creating the object.
A class that implements
+ * the nested <name>
element
+ * of a LifeCycleNestedTask
.
+ * @see LifeCycleNestedTask
+ */
+ public static class NameElement {
+
+ /** The name
attribute of this element. */
+ private String name;
+
+ /** Tells the class if we've used the overridden constructor. */
+ private boolean usedConstructor = false;
+
+ /** The empty constructor. */
+ public NameElement() {
+ // Empty
+ }
+
+ /**
+ *
Used by the LifeCycleNestedTask.createName()
method
+ * to created a nested <name>
element.
The mutator method for the name
attribute.
The accessor method for the name
attribute.
Sets the body text of the <name>
element.
+ * It contains a usage check.
At each stage in a task's life cycle, this class displays information
+ * to show the internal state of the task and its position with in the project.
+ * It takes a name
attribute.
name
attribute of this task. */
+ private String name;
+
+ /** The body text of this task. */
+ private String text;
+
+ /**
+ * The constructor displays the state of the task + * as it is instantiated.
+ */ + public LifeCycleTask() { + System.out.println("---------------"); + System.out.println("Constructor called"); + System.out.println("Value of name attribute: " + name); + System.out.println("Value of the body text: " + text); + System.out.println("Project: " + getProject()); + System.out.println("Location: " + getLocation()); + System.out.println("Target: " + getOwningTarget()); + System.out.println("---------------"); + } + + /** + *Displays the state of the task at initialization.
+ * @see #logAll(String method) + */ + public final void init() { + logAll("init()"); + } + + /** + *Displays the state of the task when Ant runs it. + * This method also runs some usage checks + * to ensure the task is being used properly.
+ */ + public final void execute() { + if (name == null) { + throw new BuildException("You must specify a name attribute in " + + getTaskName() + "."); + } + logAll("execute()"); + + // Write the name to output + log(name, Project.MSG_INFO); + } + + /** + *Sets the name to display + * and shows the state of the task afterwards.
+ * @param aName The name to display + */ + public final void setName(final String aName) { + // The value of the name attribute + this.name = aName; + logAll("setName()"); + } + + /** + *Sets the body text of the task + * and shows the state of the task afterwards.
+ * @param bodyText The body text + */ + public final void addText(final String bodyText) { + // If the body text is just whitespace, it might as well be null + if (bodyText.trim().equals("")) { + this.text = null; + } else { + this.text = bodyText.trim(); + } + logAll("addText()"); + } + + /**Checks for task references.
+ * @return String + * A string that tells us details of the reference check + */ + private String referenceCheck() { + + // The default setting + String setString = "Reference not found."; + + // We need the references that have been set in this project + Hashtable refs = getProject().getReferences(); + Enumeration e = refs.elements(); + + // Let's iterate over them + while (e.hasMoreElements()) { + // We want to work with each object, so we'll instantiate an object + Object obj = e.nextElement(); + + // Check to see whether this object is a task + // If it is, we'll build a string that contains its name and type + if (obj.getClass().getName(). + equals("org.apache.tools.ant.UnknownElement") + || + obj.getClass().getName(). + equals(this.getClass().getName())) { + + Task aTask = (Task) obj; + + setString = + "Reference to " + aTask.getTaskName() + " found, of type " + + aTask.getClass().getName() + ". "; + setString = setString + "Its id is " + + aTask.getRuntimeConfigurableWrapper(). + getAttributeMap().get("id") + "."; + } + } + return setString; + } + + /** + *A central logging method that all the life-cycle methods call
+ * to display the state of the task.
+ * It displays the value of the name
attribute
+ * and other information about the task,
+ * including the name of its project and its location in the build file.
The ProjectHelpTask
class displays usage information
+ * for the current project. This is the same information as is displayed
+ * by -projecthelp
.
Runs the task.
+ * It calls {@link org.apache.tools.ant.Main#main(String[] args)
+ * org.apache.tools.ant.Main.main()} with the -projecthelp
+ * parameter. It will also send the current build file's file name
+ * via the -f
parameter.
The buildfile
attribute is optional.
+ * The default is the task's build file.
buildfile
attribute.
+ * @param file The file name of the build file to use.
+ *
+ */
+ public final void setBuildfile(final String file) {
+ this.buildfile = file;
+ }
+
+}
+
diff --git a/ch11/ant/org/mwrm/ant/tasks/package.html b/ch11/ant/org/mwrm/ant/tasks/package.html
new file mode 100644
index 0000000..26003dc
--- /dev/null
+++ b/ch11/ant/org/mwrm/ant/tasks/package.html
@@ -0,0 +1,8 @@
+
+
+ A collection of classes that demonstrate the Ant task life cycle.
+ + diff --git a/ch11/antBook.antlib.xml b/ch11/antBook.antlib.xml new file mode 100644 index 0000000..7927073 --- /dev/null +++ b/ch11/antBook.antlib.xml @@ -0,0 +1,39 @@ + ++ + + + | ++ Apache Ant + | +
+
+ Copyright © 2000-2002, Apache Software Foundation
+
+ |
Build Failed | +Build Complete | +Total Time: |
+
+ + See the stacktrace. + |
+
ant.file | |
ant.version | |
java.version | |
os.name |
target | +task | +message | +
---|
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch11/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch11/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch11/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch11/src/shared/java/org/mwrm/plants/SelectData.java b/ch11/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch11/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch11/src/shared/java/org/mwrm/plants/package.html b/ch11/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch11/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch11/src/stand-alone/docs/index.html b/ch11/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch11/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch11/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch11/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch11/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch11/src/web/conf/antBook.xml b/ch11/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch11/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch11/src/web/java/org/mwrm/plants/servlets/package.html b/ch11/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch11/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch11/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch11/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch11/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch11/src/web/pages/footer.html b/ch11/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch11/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch11/src/web/pages/menu.jsp b/ch11/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch11/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch11/src/web/pages/template.jsp b/ch11/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch11/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
Tests the plant servlet. + * It checks that the web application is running + * and then checks the session is emptied + * if the there are no results in the query.
+ */ +public class PlantWebTest extends TestCase { + + /** The URL of the server. */ + private static final String SERVER_URL = "http://localhost:8080"; + + /** The web application's name. */ + private static final String WEB_APP = "/antBook"; + + /** The response code we're looking for. */ + private static final int RESPONSE_CODE = 200; + + /** + *The constructor,
+ * which simply calls super(name)
.
We want to make sure that the web application is running.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testIsRunning() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + try { + // Send a request to the web application's root + WebResponse resp = wc.getResponse(SERVER_URL + WEB_APP); + + // If there is a 200 return code, then it is available + assertEquals("Web application not available at " + + SERVER_URL + WEB_APP, + RESPONSE_CODE, resp.getResponseCode()); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } + + /** + *The application should detect that no results have been obtained.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testSession() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + + try { + // First send a request that will not produce any results + WebResponse resp = + wc.getResponse(SERVER_URL + WEB_APP + + "/plants/listPlants.jsp?show=name&letter=X"); + // Check that this is the case + assertTrue("Session not cancelled after empty results", + (resp.getText().indexOf("Sorry") > -1)); + + // Now go to the index page, + // where there should not be an error message + resp = wc.getResponse(SERVER_URL + WEB_APP + "/plants/"); + assertTrue("Session not cancelled after empty results", + !(resp.getText().indexOf("Sorry") > -1)); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } +} diff --git a/ch11/test/org/mwrm/plants/package.html b/ch11/test/org/mwrm/plants/package.html new file mode 100644 index 0000000..a9c3f06 --- /dev/null +++ b/ch11/test/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Contains the test classes for the plant application.
+ + diff --git a/ch12/ant/org/mwrm/ant/api/Copyer.java b/ch12/ant/org/mwrm/ant/api/Copyer.java new file mode 100644 index 0000000..bd74604 --- /dev/null +++ b/ch12/ant/org/mwrm/ant/api/Copyer.java @@ -0,0 +1,100 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.api; + +import java.io.File; +import java.io.PrintStream; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.DefaultLogger; + +import org.apache.tools.ant.taskdefs.Copy; + +import org.apache.tools.ant.types.FileSet; + +/** + *Uses a {@link FileSet FileSet} to implement a batch copy.
+ */ +public final class Copyer { + + /** + * The default constructor. + */ + private Copyer() { } + + /** + *Copies all files that match the following patterns:
+ * *.xml
and *.xsl
.
Returns the default logger for the project.
+ * @return DefaultLogger + */ + private static DefaultLogger getLogger() { + // The logger for this class + DefaultLogger logger = new DefaultLogger(); + + // The default logger needs somewhere to write to + PrintStream out = System.out; + + // Set the output streams for the logger + logger.setOutputPrintStream(out); + logger.setErrorPrintStream(out); + + // Set the message threshold for this logger + logger.setMessageOutputLevel(Project.MSG_INFO); + + return logger; + } +} diff --git a/ch12/ant/org/mwrm/ant/api/Deployer.java b/ch12/ant/org/mwrm/ant/api/Deployer.java new file mode 100644 index 0000000..08e7c40 --- /dev/null +++ b/ch12/ant/org/mwrm/ant/api/Deployer.java @@ -0,0 +1,354 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.api; + +import java.io.File; +import java.io.PrintStream; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DefaultLogger; + +import org.apache.tools.ant.listener.Log4jListener; + +import org.apache.catalina.ant.DeployTask; +import org.apache.catalina.ant.UndeployTask; + +/** + *This class deploys or undeploys a web application from the Tomcat server,
+ * using the Ant <deploy>
and <undeploy>
+ * tasks.
The following is this class's usage information:
+ * + *Deployer action [options] |
Action: |
-a, -action <deploy filename.war [-path <path>] |
+ * undeploy -path <path>> |
Options: |
-url <url> |
-u, -username <username> |
-p, -password <password> |
-l, -logfile <logfile> |
-log4j |
The deploy
option uses the {@link DeployTask DeployTask}
+ * and the undeploy
option
+ * uses the {@link UndeployTask UndeployTask}.
+ * If the user specifies the -log4j
option,
+ * this class will log with the {@link Log4jListener Log4jListener}.
The default constructor.
+ */ + private Deployer() { } + + /** The default URL for the manager application. */ + private static String managerUrl = "localhost:8080/manager"; + + /** The path. We'll build the default path below */ + private static String path = ""; + + /** The username. */ + private static String username = ""; + + /** The password. */ + private static String password = ""; + + /** The filename of the WAR. */ + private static String filename; + + /** The user's desired action. */ + private static String action = ""; + + /** Sets whether the default logger + * will use the log file orSystem.out
. */
+ private static boolean useLogFile = false;
+
+ /** The log file for the default logger. */
+ private static String logFile;
+
+ /** Sets whether we use the Log4j listener. */
+ private static boolean useLog4j = false;
+
+ /**
+ * Deploys or undeploys a web application, + * depending on which command-line options + * the user specifies.
+ * @param args The command-line arguments + */ + public static void main(final String[] args) { + + // Process the arguments and set class members + processArgs(args); + + // A final set of checks + if (action.equals("undeploy") && path.equals("")) { + usage("You must specify a path when undeploying."); + } else if (action.equals("deploy") && filename == null) { + usage("You must specify a file when deploying."); + } else if (action.equals("")) { + usage("You must specify an action with -a or -action."); + } + + // Our tasks will need a project + Project project = new Project(); + + // Add the logger + project.addBuildListener(getLogger()); + + // Does the user want to use Log4j? + if (useLog4j) { + // The listener is configured with the log4j.properties file + Log4jListener listener = new Log4jListener(); + project.addBuildListener(listener); + } + + // The deployer that will deploy the WAR file + DeployTask deployer = new DeployTask(); + // The undeployer that will undeploy the application + UndeployTask undeployer = new UndeployTask(); + + // Check what we want to do + if (action.equals("deploy")) { + // The task needs the project's logger + deployer.setProject(project); + + // Call init() as good practice + deployer.init(); + + // The name of this task + deployer.setTaskName("deployer"); + + // The next few methods set the attributes of the task + deployer.setUsername(username); + deployer.setPassword(password); + deployer.setUrl("http://" + managerUrl); + deployer.setWar("file:" + filename); + deployer.setPath(path); + deployer.setUpdate(true); + + try { + // Run the task + deployer.execute(); + } catch (BuildException be) { + // The three ways to log with a task + //System.out.println(be.getMessage()); + //project.log(be.getMessage()); + if (!(be.getMessage().indexOf("FAIL") > -1)) { + deployer.log(be.getMessage()); + } + } + } else { + // The task needs the project's logger + undeployer.setProject(project); + + // Call init() as good practice + undeployer.init(); + + // The name of this task + undeployer.setTaskName("undeployer"); + + // The next few methods set the attributes of the task + undeployer.setUsername(username); + undeployer.setPassword(password); + undeployer.setUrl("http://" + managerUrl); + undeployer.setPath(path); + + try { + // Run the task + undeployer.execute(); + //System.out.println("Undeployed " + path + "."); + } catch (BuildException be) { + // The three ways to log with a task + //System.out.println(be.getMessage()); + //project.log(be.getMessage()); + if (!(be.getMessage().indexOf("FAIL") > -1)) { + undeployer.log(be.getMessage()); + } + } + } + } // end of main() + + /** + *Processes the command-line arguments + * and sets member variables.
+ * @param args The command-line arguments + */ + private static void processArgs(final String[] args) { + try { + // We'll go through the command-line arguments + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + + // Check to see if the user has specified an action + if (arg.equals("-a") || arg.equals("-action")) { + // If it's "undeploy", we'll remember that + if (args[i + 1].equals("undeploy")) { + action = "undeploy"; + i++; + // If it's "deploy", we'll remember that + } else if (args[i + 1].equals("deploy")) { + action = "deploy"; + i++; + // If it's not "undeploy" or "deploy", it's incorrect + } else { + usage(); + } + // Check for the path + } else if (arg.equals("-path")) { + path = args[i + 1]; + i++; + // Check for the URL + } else if (arg.equals("-url")) { + managerUrl = args[i + 1]; + i++; + // Check for the username + } else if (arg.equals("-u") || arg.equals("-username")) { + username = args[i + 1]; + i++; + // Check for the password + } else if (arg.equals("-p") || arg.equals("-password")) { + password = args[i + 1]; + i++; + // Check if the user wants to use a log file + } else if (arg.equals("-l") || arg.equals("-logfile")) { + logFile = args[i + 1]; + useLogFile = true; + i++; + // Check if the user wants to use Log4j + } else if (arg.equals("-log4j")) { + useLog4j = true; + // If the user has specified any other argument, + // it's incorrect + } else if (arg.startsWith("-")) { + String msg = "Unknown argument: " + arg; + usage(msg); + // If there's no prefix it's our WAR file + // We only check for it if we're deploying + } else if (action.equals("deploy")) { + // This must be our WAR file + filename = arg; + + // Create a file object + File warFile = new File(filename); + + // Check if this file actually exists + if (!warFile.exists()) { + String msg = "File " + arg + " does not exist."; + System.out.println(msg); + System.exit(-1); + } + + // We should set the path if the user did not + // The path must begin with a '/' + if (path.equals("")) { + // If the WAR file is not in the current directory, + // there will be a slash + int begin = filename.lastIndexOf("/"); + // We'll add a slash or not + // depending on where the WAR is + String slash = ""; + + // If there is no slash, the index will be -1 + if (begin == -1) { + // Therefore, we need to take the whole file name + begin = 0; + // and add a slash to the path + slash = "/"; + } + // Build the path by removing the .war extension + path = slash + + filename + .substring(begin, filename.lastIndexOf(".war")); + } + } + } + // If a command-line options is not followed by another argument + // the checks above will throw a ArrayIndexOutOfBoundsException + } catch (ArrayIndexOutOfBoundsException aioobe) { + usage(); + } + } + + /** + *Returns the default logger for the project.
+ * @return DefaultLogger + */ + private static DefaultLogger getLogger() { + // The logger for this class + DefaultLogger logger = new DefaultLogger(); + + // The default logger needs somewhere to write to + PrintStream out = null; + + // Does the user want to write to a file? + if (useLogFile) { + try { + // We'll log to the file the user specified + out = new PrintStream(new FileOutputStream(logFile, true)); + } catch (FileNotFoundException fnfe) { + // We can't use the log just yet + System.out.println(fnfe.getMessage()); + // We'll fall back to System.out + System.out.println("Using the console."); + out = System.out; + } + } else { + // The default is to print to System.out + out = System.out; + } + + // Set the output streams for the logger + logger.setOutputPrintStream(out); + logger.setErrorPrintStream(out); + + // Set the message threshold for this logger + logger.setMessageOutputLevel(Project.MSG_INFO); + + return logger; + } + + /**Displays the usage information.
*/ + private static void usage() { + System.out.println("Usage information:"); + System.out.println("Deployer action [options]"); + System.out.println("Action:"); + String actionMsg = "-a, -actionDisplays a custom message, then usage information.
+ * @param message The message to print + */ + private static void usage(final String message) { + System.out.println(message); + usage(); + } +} diff --git a/ch12/ant/org/mwrm/ant/api/package.html b/ch12/ant/org/mwrm/ant/api/package.html new file mode 100644 index 0000000..9943883 --- /dev/null +++ b/ch12/ant/org/mwrm/ant/api/package.html @@ -0,0 +1,8 @@ + + +A package that contains classes that demonstrate the Ant API.
+ + diff --git a/ch12/ant/org/mwrm/ant/listeners/BuildEventListener.java b/ch12/ant/org/mwrm/ant/listeners/BuildEventListener.java new file mode 100644 index 0000000..15f3dd1 --- /dev/null +++ b/ch12/ant/org/mwrm/ant/listeners/BuildEventListener.java @@ -0,0 +1,116 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.listeners; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.BuildEvent; + +/** + *A class that demonstrates some of the functionality + * of a custom listener.
+ */ +public class BuildEventListener implements BuildListener { + + /** + *Signals that a build has started. This event + * is fired before any targets have started.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ */
+ public final void buildStarted(final BuildEvent start) {
+ start.getProject().log("buildStarted() called.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that the last target has finished. This event + * will still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public final void buildFinished(final BuildEvent finish) {
+ finish.getProject().log("buildFinished() called.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a target is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTarget()
+ */
+ public final void targetStarted(final BuildEvent start) {
+ start.getProject().log("Target [" + start.getTarget().getName()
+ + "] started.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a target has finished. This event will + * still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public final void targetFinished(final BuildEvent finish) {
+ finish.getProject().log("Target [" + finish.getTarget().getName()
+ + "] finished.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a task is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTask()
+ */
+ public final void taskStarted(final BuildEvent start) {
+ start.getProject().log("Task [" + start.getTask().getTaskName()
+ + "] started.", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that a task has finished. This event will still + * be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public final void taskFinished(final BuildEvent finish) {
+ finish.getProject().log("Task [" + finish.getTask().getTaskName()
+ + "] finished.", Project.MSG_ERR);
+ }
+
+ /** When a message is sent to this logger, Ant calls this method.
+ * @param event An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public void messageLogged(final BuildEvent event) {
+ // empty
+ }
+}
diff --git a/ch12/ant/org/mwrm/ant/listeners/package.html b/ch12/ant/org/mwrm/ant/listeners/package.html
new file mode 100644
index 0000000..c12041d
--- /dev/null
+++ b/ch12/ant/org/mwrm/ant/listeners/package.html
@@ -0,0 +1,8 @@
+
+
+ A package that contains custom listeners.
+ + diff --git a/ch12/ant/org/mwrm/ant/loggers/BuildEventLogger.java b/ch12/ant/org/mwrm/ant/loggers/BuildEventLogger.java new file mode 100644 index 0000000..60eb52c --- /dev/null +++ b/ch12/ant/org/mwrm/ant/loggers/BuildEventLogger.java @@ -0,0 +1,195 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.loggers; + +import java.io.PrintStream; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.BuildEvent; + +/** + *A class that demonstrates some of the functionality + * of a custom logger.
+ */ +public class BuildEventLogger implements BuildLogger { + + /** + * PrintStream to write non-error messages to. + */ + private PrintStream out; + + /** + * PrintStream to write error messages to. + */ + private PrintStream err; + + /** + * Sets whether to tailor output for Emacs, etc. + * The default isfalse
.
+ */
+ private boolean emacsMode = false;
+
+ /**
+ * We'll set this logger to log only warnings.
+ */
+ private int msgOutputLevel = Project.MSG_WARN;
+
+ /**
+ * Signals that a build has started. This event + * is fired before any targets have started.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ */
+ public final void buildStarted(final BuildEvent start) {
+ start.getProject().log("Message from buildStarted().", Project.MSG_ERR);
+ }
+
+ /**
+ * Signals that the last target has finished. This event + * will still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void buildFinished(final BuildEvent finish) {
+ // empty
+ }
+
+ /**
+ * Signals that a target is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTarget()
+ */
+ public void targetStarted(final BuildEvent start) {
+ // empty
+ }
+
+ /**
+ * Signals that a target has finished. This event will + * still be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void targetFinished(final BuildEvent finish) {
+ // empty
+ }
+
+ /**
+ * Signals that a task is starting.
+ * + * @param start An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getTask()
+ */
+ public void taskStarted(final BuildEvent start) {
+ // empty
+ }
+
+ /**
+ * Signals that a task has finished. This event will still + * be fired if an error occurred during the build.
+ * + * @param finish An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void taskFinished(final BuildEvent finish) {
+ // empty
+ }
+
+ /** When a message is sent to this logger, Ant calls this method.
+ * @param event An event with any relevant extra information. + * Must not benull
.
+ *
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public final void messageLogged(final BuildEvent event) {
+ /// We need to determine how important this message is
+ int priority = event.getPriority();
+
+ // If it's as important as our log level, we display it
+ if (priority <= msgOutputLevel) {
+ out.println("messageLogged: " + event.getMessage());
+ }
+ }
+
+ /**
+ * Sets the output stream to which this logger is to send its output.
+ * + * @param output The output stream for the logger. + * Must not benull
.
+ */
+ public final void setOutputPrintStream(final PrintStream output) {
+ this.out = new PrintStream(output, true);
+ }
+
+ /**
+ * Sets the output stream to which this logger + * is to send error messages.
+ * + * @param errorStream The error stream for the logger. + * Must not benull
.
+ */
+ public final void setErrorPrintStream(final PrintStream errorStream) {
+ this.err = new PrintStream(errorStream, true);
+ }
+
+ /**
+ * Sets this logger to produce Emacs + * (and other editor) friendly output.
+ * + * @param modetrue
if output is to be unadorned so that
+ * Emacs and other editors can parse files names, etc.
+ */
+ public final void setEmacsMode(final boolean mode) {
+ this.emacsMode = mode;
+ }
+
+ /**
+ * Sets the highest level of message this logger should respond to.
+ * + *Only messages with a message level lower than or equal to the + * given level should be written to the log.
+ *
+ * Constants for the message levels are in the
+ * {@link Project Project} class. The order of the levels, from least
+ * to most verbose, is MSG_ERR
, MSG_WARN
,
+ * MSG_INFO
, MSG_VERBOSE
,
+ * MSG_DEBUG
.
The default for this logger is + * {@link Project#MSG_WARN Project.MSG_WARN}.
+ * + * @param level the logging level for the logger. + */ + public void setMessageOutputLevel(final int level) { + // We will leave this empty to use the default level, + // which we set above + } + +} diff --git a/ch12/ant/org/mwrm/ant/loggers/package.html b/ch12/ant/org/mwrm/ant/loggers/package.html new file mode 100644 index 0000000..e30bac9 --- /dev/null +++ b/ch12/ant/org/mwrm/ant/loggers/package.html @@ -0,0 +1,8 @@ + + +A package that contains custom loggers.
+ + diff --git a/ch12/ant/org/mwrm/ant/tasks/ClassSetTask.java b/ch12/ant/org/mwrm/ant/tasks/ClassSetTask.java new file mode 100644 index 0000000..5afbdfd --- /dev/null +++ b/ch12/ant/org/mwrm/ant/tasks/ClassSetTask.java @@ -0,0 +1,60 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.mwrm.ant.tasks; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.BuildException; + +/** + *The ClassSetTask
class
+ * demonstrates how to use a Class
argument
+ * in a custom class attribute.
Runs the task and displays the qualified name of the class
+ * that is set as the setQualifiedName
attribute.
Sets the fully qualified name of the class.
+ ** @param qName The fully qualified name of a class + */ + public final void setQualifiedName(final Class qName) { + if (qName.getName().equals("java.lang.Integer") + || + qName.getName().equals("java.lang.String")) { + log(qName.getName() + " found.", Project.MSG_INFO); + } else { + String msg = "You can only specify java.lang.Integer " + + "or java.lang.String in qualifiedName."; + throw new BuildException(msg); + } + this.qualifiedName = qName; + } +} + diff --git a/ch12/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java b/ch12/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java new file mode 100644 index 0000000..e9c076c --- /dev/null +++ b/ch12/ant/org/mwrm/ant/tasks/ExtendJavadocTask.java @@ -0,0 +1,188 @@ +/* + * Extends org.apache.tools.ant.taskdefs.Javadoc + * and uses org.apache.tools.ant.taskdefs.UpToDate, + * which are Copyright 2000-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.ant.tasks; + +import java.io.File; + +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildException; + +import org.apache.tools.ant.taskdefs.Javadoc; +import org.apache.tools.ant.taskdefs.UpToDate; + +import org.apache.tools.ant.types.FileSet; + +/** + *The ExtendJavadocTask
class
+ * extends the {@link org.apache.tools.ant.taskdefs.Javadoc Javadoc} task.
+ * It checks whether a set of source files are newer than a set of target files
+ * and if so, it generates Javadocs.
Creates a new instance of an internal
+ * <uptodate>
task
+ * and adds it to the current project.
Checks if Javadocs should be created
+ * and then calls super.execute()
if so.
This method does usage checks on the task's attributes
+ * and its nested elements.
+ * It will throw a BuildException
if there is a violation.
Checks whether the files are up to date.
+ * @param file The file to evaluate + * @return boolean The result + */ + private boolean getResult(final File file) { + // Set the target property in theThe setter method for the target
attribute.
The setter method for the file set
+ * contained in the nested <srcfiles>
element.
The setter method for the file sets
+ * contained in nested <targetfiles>
elements.
At each stage in a task's life cycle, this class displays information
+ * to show the internal state of the task and its position with in the project.
+ * It takes a name
attribute.
This class demonstrates how to nest elements within a custom task.
+ * Its nested element is called <name>
+ * and cannot be used in conjunction with a name
attribute.
The constructor displays the state of the task + * as it is instantiated.
+ */ + public LifeCycleNestedTask() { + System.out.println("---------------"); + System.out.println("Constructor called"); + System.out.println("Value of name attribute: " + this.name); + System.out.println("Value of the body text: " + text); + System.out.println("Project: " + getProject()); + System.out.println("Location: " + getLocation()); + System.out.println("Target: " + getOwningTarget()); + System.out.println("---------------"); + } + + /** + *Displays the state of the task at initialization.
+ * @see #logAll(String method) + */ + public final void init() { + logAll("init()"); + } + + /** + *Displays the state of the task when Ant runs it. + * This method also runs some usage checks + * to ensure the task is being used properly.
+ */ + public final void execute() { + if (name != null && nameElements.size() > 0) { + String msg = "You can't specify a name attribute " + + "andSets the name to display + * and shows the state of the task afterwards.
+ * @param aName The name to display + */ + public final void setName(final String aName) { + // The value of the name attribute + this.name = aName; + logAll("setName()"); + } + + /** + *Sets the body text of the task + * and shows the state of the task afterwards.
+ * @param bodyText The body text + */ + public final void addText(final String bodyText) { + // If the body text is just whitespace, it might as well be null + if (bodyText.trim().equals("")) { + this.text = null; + } else { + this.text = bodyText.trim(); + } + logAll("addText()"); + } + + /**Checks for task references.
+ * @return String + * A string that tells us details of the reference check + */ + private String referenceCheck() { + + // The default setting + String setString = "Reference not found."; + + // We need the references that have been set in this project + Hashtable refs = getProject().getReferences(); + Enumeration e = refs.elements(); + + // Let's iterate over them + while (e.hasMoreElements()) { + // We want to work with each object, so we'll instantiate an object + Object obj = e.nextElement(); + + // Check to see if this object is a custom task + // If it is, we'll build a string that contains its name and type + if (obj.getClass().getName(). + equals("org.apache.tools.ant.UnknownElement") + || + obj.getClass().getName(). + equals(this.getClass().getName())) { + Task aTask = (Task) obj; + setString = + "Reference to " + aTask.getTaskName() + " found, of type " + + aTask.getClass().getName() + ". "; + setString = setString + "Its id is " + + aTask.getRuntimeConfigurableWrapper(). + getAttributeMap().get("id") + "."; + } + } + return setString; + } + + /** + *A central logging method that all the life-cycle methods call
+ * to display the state of the task.
+ * It displays the value of the name
attribute
+ * and other information about the task,
+ * including the name of its project and its location in the build file.
Adds a <name>
element
+ * that has not been initialized.
NameElement
+ * object that represents the nested element
+ * @see LifeCycleNestedTask.NameElement
+ */
+ public final void addName(final NameElement nameElement) {
+ nameElements.add(nameElement);
+
+ logAll("addName()");
+ log("Value of this name: "
+ + nameElement.getName(), Project.MSG_VERBOSE);
+ }
+
+ /**
+ * Adds a <name>
element
+ * that has been initialized.
NameElement
+ * object that represents the nested element
+ * @see LifeCycleNestedTask.NameElement
+ */
+ public final void addConfiguredName(final NameElement nameElement) {
+ nameElements.add(nameElement);
+
+ logAll("addConfiguredName()");
+ log("Value of this name: " + nameElement.getName(),
+ Project.MSG_VERBOSE);
+ }
+
+ /**
+ * Adds a <name>
element
+ * that has not been initialized.
+ * In this case, the createName()
method
+ * has the responsibility
+ * for creating the object.
A class that implements
+ * the nested <name>
element
+ * of a LifeCycleNestedTask
.
+ * @see LifeCycleNestedTask
+ */
+ public static class NameElement {
+
+ /** The name
attribute of this element. */
+ private String name;
+
+ /** Tells the class if we've used the overridden constructor. */
+ private boolean usedConstructor = false;
+
+ /** The empty constructor. */
+ public NameElement() {
+ // Empty
+ }
+
+ /**
+ *
Used by the LifeCycleNestedTask.createName()
method
+ * to created a nested <name>
element.
The mutator method for the name
attribute.
The accessor method for the name
attribute.
Sets the body text of the <name>
element.
+ * It contains a usage check.
At each stage in a task's life cycle, this class displays information
+ * to show the internal state of the task and its position with in the project.
+ * It takes a name
attribute.
name
attribute of this task. */
+ private String name;
+
+ /** The body text of this task. */
+ private String text;
+
+ /**
+ * The constructor displays the state of the task + * as it is instantiated.
+ */ + public LifeCycleTask() { + System.out.println("---------------"); + System.out.println("Constructor called"); + System.out.println("Value of name attribute: " + name); + System.out.println("Value of the body text: " + text); + System.out.println("Project: " + getProject()); + System.out.println("Location: " + getLocation()); + System.out.println("Target: " + getOwningTarget()); + System.out.println("---------------"); + } + + /** + *Displays the state of the task at initialization.
+ * @see #logAll(String method) + */ + public final void init() { + logAll("init()"); + } + + /** + *Displays the state of the task when Ant runs it. + * This method also runs some usage checks + * to ensure the task is being used properly.
+ */ + public final void execute() { + if (name == null) { + throw new BuildException("You must specify a name attribute in " + + getTaskName() + "."); + } + logAll("execute()"); + + // Write the name to output + log(name, Project.MSG_INFO); + } + + /** + *Sets the name to display + * and shows the state of the task afterwards.
+ * @param aName The name to display + */ + public final void setName(final String aName) { + // The value of the name attribute + this.name = aName; + logAll("setName()"); + } + + /** + *Sets the body text of the task + * and shows the state of the task afterwards.
+ * @param bodyText The body text + */ + public final void addText(final String bodyText) { + // If the body text is just whitespace, it might as well be null + if (bodyText.trim().equals("")) { + this.text = null; + } else { + this.text = bodyText.trim(); + } + logAll("addText()"); + } + + /**Checks for task references.
+ * @return String + * A string that tells us details of the reference check + */ + private String referenceCheck() { + + // The default setting + String setString = "Reference not found."; + + // We need the references that have been set in this project + Hashtable refs = getProject().getReferences(); + Enumeration e = refs.elements(); + + // Let's iterate over them + while (e.hasMoreElements()) { + // We want to work with each object, so we'll instantiate an object + Object obj = e.nextElement(); + + // Check to see whether this object is a task + // If it is, we'll build a string that contains its name and type + if (obj.getClass().getName(). + equals("org.apache.tools.ant.UnknownElement") + || + obj.getClass().getName(). + equals(this.getClass().getName())) { + + Task aTask = (Task) obj; + + setString = + "Reference to " + aTask.getTaskName() + " found, of type " + + aTask.getClass().getName() + ". "; + setString = setString + "Its id is " + + aTask.getRuntimeConfigurableWrapper(). + getAttributeMap().get("id") + "."; + } + } + return setString; + } + + /** + *A central logging method that all the life-cycle methods call
+ * to display the state of the task.
+ * It displays the value of the name
attribute
+ * and other information about the task,
+ * including the name of its project and its location in the build file.
The ProjectHelpTask
class displays usage information
+ * for the current project. This is the same information as is displayed
+ * by -projecthelp
.
Runs the task.
+ * It calls {@link org.apache.tools.ant.Main#main(String[] args)
+ * org.apache.tools.ant.Main.main()} with the -projecthelp
+ * parameter. It will also send the current build file's file name
+ * via the -f
parameter.
The buildfile
attribute is optional.
+ * The default is the task's build file.
buildfile
attribute.
+ * @param file The file name of the build file to use.
+ *
+ */
+ public final void setBuildfile(final String file) {
+ this.buildfile = file;
+ }
+
+}
+
diff --git a/ch12/ant/org/mwrm/ant/tasks/package.html b/ch12/ant/org/mwrm/ant/tasks/package.html
new file mode 100644
index 0000000..26003dc
--- /dev/null
+++ b/ch12/ant/org/mwrm/ant/tasks/package.html
@@ -0,0 +1,8 @@
+
+
+ A collection of classes that demonstrate the Ant task life cycle.
+ + diff --git a/ch12/antBook.antlib.xml b/ch12/antBook.antlib.xml new file mode 100644 index 0000000..7927073 --- /dev/null +++ b/ch12/antBook.antlib.xml @@ -0,0 +1,39 @@ + ++ + + + | ++ Apache Ant + | +
+
+ Copyright © 2000-2002, Apache Software Foundation
+
+ |
Build Failed | +Build Complete | +Total Time: |
+
+ + See the stacktrace. + |
+
ant.file | |
ant.version | |
java.version | |
os.name |
target | +task | +message | +
---|
+ The stand-alone application + The web application + |
+ This is the Plant Application. |
+
Constants
class contains four constants
+ * that represent sort options.
+ *
+ */
+
+public class Constants {
+
+ /** Use to sort the plants by their botanical name. */
+ public static final int SORT_BY_NAME = 1;
+
+ /** Use to sort the plants by their common name. */
+ public static final int SORT_BY_COMMON_NAME = 2;
+
+ /** Use to sort the plants by their family name. */
+ public static final int SORT_BY_FAMILY = 3;
+
+ /**
+ * Use to sort the plants by their botanical name
+ * and exclude those plants that do not begin with the chosen letter.
+ */
+ public static final int SORT_BY_CHOSEN_LETTER = 4;
+
+ /**
+ * A simple constructor.
+ */
+ public Constants() { }
+
+}
diff --git a/ch12/src/shared/java/org/mwrm/plants/PropertiesLoader.java b/ch12/src/shared/java/org/mwrm/plants/PropertiesLoader.java
new file mode 100644
index 0000000..10d07f3
--- /dev/null
+++ b/ch12/src/shared/java/org/mwrm/plants/PropertiesLoader.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.util.Properties;
+
+import java.io.IOException;
+
+/**
+ * The PropertiesLoader
class loads properties
+ * from the database.properties
file and passes them
+ * to whichever class wants to use them. This centralises the name
+ * of the properties file so the entire application can use it.
+ */
+
+public class PropertiesLoader {
+
+ /** A simple constructor. */
+ public PropertiesLoader() { }
+
+ /**
+ * Loads the properties for whichever class needs them.
+ *
+ * @return A Java properties file
+ */
+ public final Properties loadProperties() {
+
+ // Read properties file.
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getClassLoader()
+ .getResourceAsStream("database.properties"));
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+
+ return properties;
+ }
+}
diff --git a/ch12/src/shared/java/org/mwrm/plants/SelectData.java b/ch12/src/shared/java/org/mwrm/plants/SelectData.java
new file mode 100644
index 0000000..5a0ad89
--- /dev/null
+++ b/ch12/src/shared/java/org/mwrm/plants/SelectData.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.mwrm.plants;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.Properties;
+
+/**
+ * The SelectData
class establishes a connection
+ * with a database and executes a query, as selected by the client.
It gets the database driver name and the URL
+ * from the database.properties
file.
+ * When the results come back from the database,
+ * this class places them as HashMap
records
+ * in a Vector
. It then passes this Vector
+ * back to the calling client.
The SQL strings are:
+ * + *Constants.SORT_BY_NAME
(the default):
+ * SELECT * FROM plants ORDER BY name
Constants.SORT_BY_COMMON_NAME
:
+ * SELECT * FROM plants ORDER BY common_name
Constants.SORT_BY_FAMILY
:
+ * SELECT * FROM plants ORDER BY family, name
Constants.SORT_BY_CHOSEN_LETTER
:
+ * SELECT * FROM plants WHERE name REGEXP '^X'
+ * where X
is a letter supplied by the clientThe default constructor.
+ */ + private SelectData() { } + + /** + * Get the data from the database. + * @param choice The criteria for sorting the results. + * This choice is held in theConstants
class.
+ * @param letter The letter to use when limiting the search,
+ * should that option be chosen.
+ * @return Vector
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there's a problem with database operations
+ */
+ public static Vector getData(final int choice, final String letter)
+ throws ClassNotFoundException, SQLException {
+
+ // Read properties file
+ PropertiesLoader loader = new PropertiesLoader();
+ Properties properties = loader.loadProperties();
+
+ // First load the MySQL JDBC driver
+ Class.forName(properties.getProperty("driver.name"));
+
+ // The datasource
+ String url = properties.getProperty("database.root")
+ + properties.getProperty("database.name");
+
+ // Open the connection
+ Connection con = DriverManager.getConnection(url, "antBook", "antB00k");
+
+ Statement stmt = con.createStatement();
+
+ String select = getSelectString(choice, letter);
+
+ // Now we get the data
+ ResultSet rs = stmt.executeQuery(select);
+
+ // We'll need the metadata when we come to populate the session object
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int numberOfColumns = rsmd.getColumnCount();
+
+ Vector results = new Vector();
+
+ while (rs.next()) {
+ // We need a fresh entry every time
+ HashMap record = new HashMap(numberOfColumns);
+
+ String columnName = "";
+
+ // For each column in the table,
+ // we want to add an entry to the HashMap
+ // with the same key as the column name
+ for (int i = 1; i <= numberOfColumns; i++) {
+ columnName = rsmd.getColumnName(i);
+ record.put(columnName, rs.getString(columnName));
+ }
+ results.add(record);
+ }
+
+ // Close the Statement and the Connection
+ stmt.close();
+ con.close();
+
+ return results;
+ }
+
+ /**
+ * Returns the appropriate SQL string for the choice.
+ * @param choice The user's choice of search criteria. + * @param letter The letter to use when modifying the search. + * @return String + */ + private static String + getSelectString(final int choice, final String letter) { + + // This is the default SELECT statement if no arguments are specified + String selectString = "SELECT * FROM plants ORDER BY name"; + + // Check the type of argument + if (choice == Constants.SORT_BY_COMMON_NAME) { + // Order the results by common name + selectString = "SELECT * FROM plants ORDER BY common_name"; + + } else if (choice == Constants.SORT_BY_FAMILY) { + // Order the results by family, then botanical name + selectString = "SELECT * FROM plants ORDER BY family, name"; + + } else if (choice == Constants.SORT_BY_CHOSEN_LETTER) { + // The search will only return those plants whose botanical name + // begins with the specifed letter. + selectString = "SELECT * FROM plants WHERE name REGEXP '^" + + letter + "'"; + } + + return selectString; + } +} diff --git a/ch12/src/shared/java/org/mwrm/plants/package.html b/ch12/src/shared/java/org/mwrm/plants/package.html new file mode 100644 index 0000000..b64e7c3 --- /dev/null +++ b/ch12/src/shared/java/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Utility classes for the plant application.
+ + \ No newline at end of file diff --git a/ch12/src/stand-alone/docs/index.html b/ch12/src/stand-alone/docs/index.html new file mode 100644 index 0000000..f615152 --- /dev/null +++ b/ch12/src/stand-alone/docs/index.html @@ -0,0 +1,19 @@ + + + +
+ The stand-alone application + The web application + |
+ This is the stand-alone application. |
+
The PlantClient
class is a command-line client
+ * for the plant application.
Usage:
+ *-c
Order by common name-f
Order by family-n
Order by botanical name (default)-n [letter]
Order by botanical name
+ * and limit the search to plants beginning with the specified letterA simple constructor.
+ */ + private PlantClient() { } + + /** + * Checks the arguments, + * then uses theorg.mwrm.plants.SelectData
+ * class to get results from the database.
+ * Once it has the results, it displays them to standard out
.
+ * @param args The command-line arguments.
+ * @throws ClassNotFoundException If the database driver is not found
+ * @throws SQLException If there is a problem with the database
+ */
+ public static void main(final String[] args)
+ throws ClassNotFoundException, SQLException {
+
+ // The default choice
+ int choice = Constants.SORT_BY_NAME;
+
+ // The user may want to select by a certain letter
+ String letter = "";
+
+ // Check that an argument has been provided
+ if (args.length > 0) {
+ // Check the type of argument
+ if (args[0].equals("-c") || args[0].equals("")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by common name
+ choice = Constants.SORT_BY_COMMON_NAME;
+
+ } else if (args[0].equals("-f")) {
+ // Cannot be used with an argument just now,
+ // though Ant may pass an empty string
+ if (args.length > 1 && !args[1].equals("")) {
+ usage();
+ }
+ // Order the results by family, then botanical name
+ choice = Constants.SORT_BY_FAMILY;
+
+ } else if (args[0].equals("-n")) {
+ // Order the results by botanical name
+ // This is the default if no arguments are specified
+
+ // The user can provide another argument.
+ // The search will only return those plants whose botanical name
+ // begins with the specifed letter.
+ if (args.length > 1 && !args[1].equals("")) {
+ choice = Constants.SORT_BY_CHOSEN_LETTER;
+ letter = args[1];
+ }
+ } else {
+ // Usage information
+ usage();
+ }
+ }
+
+ // Obtain the results. This is a Vector of HashMaps
+ Vector results = SelectData.getData(choice, letter);
+
+ // The top of the results display
+ System.out.println("\n-----------------------------");
+
+ // If there is no data in the results, tell the user
+ if (results.isEmpty()) {
+ System.out.println("No results found.");
+ System.out.println("-----------------------------");
+ } else {
+
+ // Each record in the database is a HashMap
+ HashMap record = new HashMap();
+
+ // Iterate over the results
+ for (Enumeration enum = results.elements();
+ enum.hasMoreElements();) {
+
+ record = (HashMap) enum.nextElement();
+
+ // The cultivar name is optional
+ String cultivar = "";
+
+ if (!(record.get("cultivar_name") == null)) {
+ cultivar = " '" + record.get("cultivar_name") + "'";
+ }
+
+ System.out.println("Name: " + record.get("name") + cultivar);
+ System.out.println("Common name: " + record.get("common_name"));
+ System.out.println("Family: " + record.get("family"));
+ System.out.println("Description: " + record.get("description"));
+ System.out.println("-----------------------------");
+ }
+ }
+ }
+
+ /**
+ * Print the usage information.
+ */ + private static void usage() { + System.out.println("\nUsage: \n"); + System.out.println("-c \t\t Order by common name"); + System.out.println("-f \t\t Order by family"); + System.out.println("-n \t\t Order by botanical name (default)"); + System.out.println("-n [letter] \t Order by botanical name" + + " and limit the search to plants "); + System.out.println("\t\t beginning with the specified letter"); + System.exit(0); + } +} diff --git a/ch12/src/stand-alone/java/org/mwrm/plants/client/package.html b/ch12/src/stand-alone/java/org/mwrm/plants/client/package.html new file mode 100644 index 0000000..9148475 --- /dev/null +++ b/ch12/src/stand-alone/java/org/mwrm/plants/client/package.html @@ -0,0 +1,8 @@ + + +Contains the command-line client for the plant application.
+ + \ No newline at end of file diff --git a/ch12/src/web/conf/antBook.xml b/ch12/src/web/conf/antBook.xml new file mode 100644 index 0000000..9746019 --- /dev/null +++ b/ch12/src/web/conf/antBook.xml @@ -0,0 +1,3 @@ +
+ The stand-alone application + The web application + |
+ This is the web application. |
+
The servlet client for the plant application.
+ * + *
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Extracts the show
parameter from the request
+ * to determine what the user wants to see.
+ * Valid values for show
are:
common
: Order by common namefamily
: Order by familyname
: Order by botanical name (default)If the client sends a letter
parameter,
+ * then the search is limited to records that begin with that letter.
Once the choice has been extracted,
+ * this servlet uses the org.mwrm.plants.SelectData
class
+ * to get results from the database. Once it has the results,
+ * it places them in the session under the name "results"
+ * and forwards the request to /plants/displayPage.jsp
,
+ * which displays the first page of the results.
If the debug
servlet initialization parameter
+ * is set to true
the results
+ * will also be sent to standard out
.
POST
requests to the doGet
method.
+ *
+ * @param request The request object.
+ * @param response The response object.
+ *
+ * @throws ServletException
+ * If there is a problem when processing the request
+ * @throws IOException If there is a problem writing the response
+ */
+ public final void doPost(final HttpServletRequest request,
+ final HttpServletResponse response)
+ throws ServletException, IOException {
+ doGet(request, response);
+ }
+}
diff --git a/ch12/src/web/java/org/mwrm/plants/servlets/package.html b/ch12/src/web/java/org/mwrm/plants/servlets/package.html
new file mode 100644
index 0000000..d9ac8dd
--- /dev/null
+++ b/ch12/src/web/java/org/mwrm/plants/servlets/package.html
@@ -0,0 +1,8 @@
+
+
+ Contains the servlet for the plant application.
+ + \ No newline at end of file diff --git a/ch12/src/web/java/org/mwrm/plants/tags/LettersTag.java b/ch12/src/web/java/org/mwrm/plants/tags/LettersTag.java new file mode 100644 index 0000000..6911f3f --- /dev/null +++ b/ch12/src/web/java/org/mwrm/plants/tags/LettersTag.java @@ -0,0 +1,63 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.mwrm.plants.tags; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.SimpleTagSupport; + +import java.io.IOException; + + /** + *Converts an integer into a character.
+ * The letter
attribute takes the integer,
+ * which is converted to a char by the time the tag gets it.
+ * The tag then writes the char to the client.
Processes the tag when it is encountered on the page.
+ * @throws JspException + * If there is a problem processing the tag + * @throws IOException + * If there is a problem writing to the client + */ + public final void doTag() throws JspException, IOException { + + // The page that the client will receive + JspWriter out = getJspContext().getOut(); + + // Write the letter to the client + out.print(letter); + } + + /** + * + *The setter method for the letter
attribute.
Contains the custom tags for the plant application.
+ + \ No newline at end of file diff --git a/ch12/src/web/pages/footer.html b/ch12/src/web/pages/footer.html new file mode 100644 index 0000000..8511c9c --- /dev/null +++ b/ch12/src/web/pages/footer.html @@ -0,0 +1,3 @@ +
+
Welcome to the plant information application.
\ No newline at end of file diff --git a/ch12/src/web/pages/menu.jsp b/ch12/src/web/pages/menu.jsp new file mode 100644 index 0000000..135b8cc --- /dev/null +++ b/ch12/src/web/pages/menu.jsp @@ -0,0 +1,5 @@ +<%-- This page is common to the whole application --%> +<%@ taglib tagdir="/WEB-INF/tags" prefix="tags" %> ++ |
+ |
+
+ Name: |
+
Sorry, there were no results for the search. Please try again.
+Number of results:
+ + + |
+
+ |
+
Click on a letter above or a link on the left.
diff --git a/ch12/src/web/pages/template.jsp b/ch12/src/web/pages/template.jsp new file mode 100644 index 0000000..a1fff41 --- /dev/null +++ b/ch12/src/web/pages/template.jsp @@ -0,0 +1,47 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> + + + + + + +
+
|
+ ||
+ Home
+ Plants
+
+
+ By botanical name
+ By family
+
Tests the plant servlet. + * It checks that the web application is running + * and then checks the session is emptied + * if the there are no results in the query.
+ */ +public class PlantWebTest extends TestCase { + + /** The URL of the server. */ + private static final String SERVER_URL = "http://localhost:8080"; + + /** The web application's name. */ + private static final String WEB_APP = "/antBook"; + + /** The response code we're looking for. */ + private static final int RESPONSE_CODE = 200; + + /** + *The constructor,
+ * which simply calls super(name)
.
We want to make sure that the web application is running.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testIsRunning() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + try { + // Send a request to the web application's root + WebResponse resp = wc.getResponse(SERVER_URL + WEB_APP); + + // If there is a 200 return code, then it is available + assertEquals("Web application not available at " + + SERVER_URL + WEB_APP, + RESPONSE_CODE, resp.getResponseCode()); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } + + /** + *The application should detect that no results have been obtained.
+ * @throws MalformedURLException + * If the URL of the web server is not correct + * @throws SAXException + * If the response can't be processed properly + */ + public final void testSession() + throws MalformedURLException, SAXException { + // Create a WebConversation object + WebConversation wc = new WebConversation(); + + try { + // First send a request that will not produce any results + WebResponse resp = + wc.getResponse(SERVER_URL + WEB_APP + + "/plants/listPlants.jsp?show=name&letter=X"); + // Check that this is the case + assertTrue("Session not cancelled after empty results", + (resp.getText().indexOf("Sorry") > -1)); + + // Now go to the index page, + // where there should not be an error message + resp = wc.getResponse(SERVER_URL + WEB_APP + "/plants/"); + assertTrue("Session not cancelled after empty results", + !(resp.getText().indexOf("Sorry") > -1)); + } catch (IOException ioe) { + // We can't find the server, so we fail the test + fail("Server not available"); + } + } +} diff --git a/ch12/test/org/mwrm/plants/package.html b/ch12/test/org/mwrm/plants/package.html new file mode 100644 index 0000000..a9c3f06 --- /dev/null +++ b/ch12/test/org/mwrm/plants/package.html @@ -0,0 +1,8 @@ + + +Contains the test classes for the plant application.
+ + diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..f6005ad --- /dev/null +++ b/contributing.md @@ -0,0 +1,14 @@ +# Contributing to Apress Source Code + +Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. + +## How to Contribute + +1. Make sure you have a GitHub account. +2. Fork the repository for the relevant book. +3. Create a new branch on which to make your change, e.g. +`git checkout -b my_code_contribution` +4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. +5. Submit a pull request. + +Thank you for your contribution! \ No newline at end of file