Friday, October 16, 2009

10.7. static Class Members



10.7. static Class Members


There is an important exception to the
rule that each object of a class has its own copy of all the data members of the
class. In certain cases, only one copy of a variable should be shared by all
objects of a class. A static data member is used for
these and other reasons. Such a variable represents "class-wide" information
(i.e., a property of the class shared by all instances, not a property of a
specific object of the class). The declaration of a static member begins with keyword static. Recall that the versions of class GradeBook
in Chapter
7 use static data members to store
constants representing the number of grades that all GradeBook objects
can hold.


Let us further motivate the need for static class-wide data with an example. Suppose that we have a video
game with Martians and other space creatures. Each Martian tends to be brave and willing to attack other space
creatures when the Martian is aware that there are
at least five Martians present. If fewer than five are present, each
Martian becomes cowardly. So each Martian needs to know the
martianCount. We could endow each instance of class Martian
with martianCount as a data member. If we do, every Martian will have a separate copy of the data member. Every time
we create a new Martian, we'll have to update the data member martianCount in all
Martian objects. Doing this would require
every Martian object to have, or have access
to, handles to all other Martian objects in
memory. This wastes space with the redundant copies and wastes time in updating
the separate copies. Instead, we declare martianCount to be
static. This makes martianCount class-wide data. Every
Martian can access martianCount as if it
were a data member of the Martian, but only one copy of the
static variable martianCount is maintained by
C++. This saves space. We save time by having the Martian constructor
increment static variable martianCount and having the
Martian destructor decrement martianCount. Because there is only one copy, we do not have to
increment or decrement separate copies of martianCount for each
Martian object.



Performance Tip 10.3








Use static data members to save storage when a single copy of the
data for all objects of a class will
suffice.



Although they may seem like global variables, a class's
static data members have class scope. Also, static members can
be declared public, private or protected. A
fundamental-type static data member is initialized by default to
0. If you want a different initial value, a static data member
can be initialized once (and only once). A
const static data member of int or enum type can be initialized in its declaration in the class
definition. However, all other static data
members must be defined at file scope (i.e., outside the body of the class
definition) and can be initialized only in those definitions. Note that
static data members of class types (i.e.,
static member objects) that have
default constructors need not be initialized because their default constructors
will be called.


A class's private and protected static members are normally accessed through
public member functions of the class or through
friends of the class. (In Chapter
12, we'll see that a class's private and protected static
members can also be accessed through protected member functions of the
class.) A class's static members exist even when
no objects of that class exist. To access a public static class member when no objects of the class exist, simply
prefix the class name and the binary scope resolution operator (::) to the name of the data member. For example, if our
preceding variable martianCount is public, it can be accessed
with the expression Martian::martianCount when there are no
Martian objects. (Of course, using public data is
discouraged.)


A class's public static class
members can also be accessed through any object of that class using the object's
name, the dot operator and the name of the member (e.g.,
myMartian.martianCount). To access a
private or protected static class
member when no objects of the class exist, provide a public static member function and call the function by prefixing its name
with the class name and binary scope resolution operator. (As we'll see in Chapter
12, a protected static member function can
serve this purpose, too.) A static member
function is a service of the class, not of a
specific object of the class.



Software Engineering Observation 10.11








A class's static data members
and static member functions exist and can be
used even if no objects of that class have been
instantiated.



The program of Figs. 10.21–10.23 demonstrates a private
static
data member called count (Fig. 10.21, line 21) and a public
static
member function called getCount (Fig. 10.21, line 15). In Fig. 10.22, line 14
defines and initializes the data member count to zero at file
scope
and lines 18–21 define static member function
getCount. Notice that neither line 14 nor line 18
includes keyword static, yet both lines refer to
static class members. When static is
applied to an item at file scope, that item becomes known only in that file. The
static members of the class need to be
available from any client code that accesses the file, so we cannot declare them
static in the .cpp file—we declare them static only
in the .h file. Data member count
maintains a count of the number of objects of class Employee that have been instantiated. When objects of class
Employee exist, member count
can be referenced through any member function of an Employee object—in
Fig. 10.22,
count is referenced by both line 33 in the
constructor and line 48 in the destructor. Also, note that since count
is an int, it could have been initialized in the
header file in line 21 of Fig 10.21











Fig. 10.21. Employee class definition with a
static data member to track the number of Employee objects in
memory.


 

 1   // Fig. 10.21: Employee.h
2 // Employee class definition.
3 #ifndef EMPLOYEE_H
4 #define EMPLOYEE_H
5
6 class Employee
7 {
8 public:
9 Employee( const char * const, const char * const ); // constructor
10 ~Employee(); // destructor
11 const char *getFirstName() const; // return first name
12 const char *getLastName() const; // return last name
13
14 // static member function
15 static int getCount(); // return number of objects instantiated
16 private:
17 char *firstName;
18 char *lastName;
19
20 // static data
21 static int count; // number of objects instantiated
22 }; // end class Employee
23
24 #endif













Fig. 10.22. Employee class member-function
definitions.


 

 1   // Fig. 10.22: Employee.cpp
2 // Employee class member-function definitions.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <cstring> // strlen and strcpy prototypes
8 using std::strlen;
9 using std::strcpy;
10
11 #include "Employee.h" // Employee class definition
12
13 // define and initialize static data member at file scope
14 int Employee::count = 0; // cannot include keyword static
15
16 // define static member function that returns number of
17 // Employee objects instantiated (declared static in Employee.h)
18 int Employee::getCount()
19 {
20 return count;
21 } // end static function getCount
22
23 // constructor dynamically allocates space for first and last name and
24 // uses strcpy to copy first and last names into the object
25 Employee::Employee( const char * const first, const char * const last )
26 {
27 firstName = new char[ strlen( first ) + 1 ]; // create space
28 strcpy( firstName, first ); // copy first into object
29
30 lastName = new char[ strlen( last ) + 1 ]; // create space
31 strcpy( lastName, last ); // copy last into object
32
33 count++; // increment static count of employees
34
35 cout << "Employee constructor for " << firstName
36 << ' ' << lastName << " called." << endl;
37 } // end Employee constructor
38
39 // destructor deallocates dynamically allocated memory
40 Employee::~Employee()
41 {
42 cout << "~Employee() called for " << firstName
43 << ' ' << lastName << endl;
44
45 delete [] firstName; // release memory
46 delete [] lastName; // release memory
47
48 count--; // decrement static count of employees
49 } // end ~Employee destructor
50
51 // return first name of employee
52 const char *Employee::getFirstName() const
53 {
54 // const before return type prevents client from modifying
55 // private data; client should copy returned string before
56 // destructor deletes storage to prevent undefined pointer
57 return firstName;
58 } // end function getFirstName
59
60 // return last name of employee
61 const char *Employee::getLastName() const
62 {
63 // const before return type prevents client from modifying
64 // private data; client should copy returned string before
65 // destructor deletes storage to prevent undefined pointer
66 return lastName;
67 } // end function getLastName





Common Programming Error 10.10








It is a compilation
error to include keyword static in the definition of a
static data members at file
scope.



In Fig. 10.22, note the use of the
new operator (lines 27 and 30) in the Employee constructor to dynamically allocate the correct amount of
memory for members firstName and lastName. If
the new operator is unable to fulfill the
request for memory for one or both of these character arrays, the program will
terminate immediately. In Chapter
16, we'll provide a better mechanism for dealing
with cases in which new is unable to allocate
memory.


Also note in Fig. 10.22
that the implementations of functions getFirstName (lines 52–58) and
getLastName (lines 61–67) return pointers to const character data. In this implementation, if the client wishes
to retain a copy of the first name or last name, the client is responsible for
copying the dynamically allocated memory in the Employee object after
obtaining the pointer to const character data
from the object. It is also possible to implement getFirstName and
getLastName, so the client is required to
pass a character array and the size of the array to each function. Then the
functions could copy the first or last name into the character array provided by
the client. Once again, note that we could have used class string here
to return a copy of a string object to the caller
rather than returning a pointer to the private data.


Figure
10.23 uses static member function
getCount to determine the number of
Employee objects currently instantiated. Note
that when no objects are instantiated in the program, the
Employee::getCount() function call is issued
(lines 14 and 38). However, when objects are instantiated, function
getCount can be called through either of
the objects, as shown in the statement in lines 22–23, which uses pointer
e1Ptr to invoke function getCount. Note that using
e2Ptr->getCount() or Employee::getCount() in line 23 would
produce the same result, because getCount always accesses the same
static member count.













Fig. 10.23. static data member tracking the
number of objects of a class.


 

 1   // Fig. 10.23: fig10_23.cpp
2 // static data member tracking the number of objects of a class.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include "Employee.h" // Employee class definition
8
9 int main()
10 {
11 // use class name and binary scope resolution operator to
12 // access static number function getCount
13 cout << "Number of employees before instantiation of any objects is "
14 << Employee::getCount() << endl; // use class name
15
16 // use new to dynamically create two new Employees
17 // operator new also calls the object's constructor
18 Employee *e1Ptr = new Employee( "Susan", "Baker" );
19 Employee *e2Ptr = new Employee( "Robert", "Jones" );
20
21 // call getCount on first Employee object
22 cout << "Number of employees after objects are instantiated is "
23 << e1Ptr->getCount();
24
25 cout << "\n\nEmployee 1: "
26 << e1Ptr->getFirstName() << " " << e1Ptr->getLastName()
27 << "\nEmployee 2: "
28 << e2Ptr->getFirstName() << " " << e2Ptr->getLastName() << "\n\n";
29
30 delete e1Ptr; // deallocate memory
31 e1Ptr = 0; // disconnect pointer from free-store space
32 delete e2Ptr; // deallocate memory
33 e2Ptr = 0; // disconnect pointer from free-store space
34
35 // no objects exist, so call static member function getCount again
36 // using the class name and the binary scope resolution operator
37 cout << "Number of employees after objects are deleted is "
38 << Employee::getCount() << endl;
39 return 0;
40 } // end main



Number of employees before instantiation of any objects is 0
Employee constructor for Susan Baker called.
Employee constructor for Robert Jones called.
Number of employees after objects are instantiated is 2

Employee 1: Susan Baker
Employee 2: Robert Jones

~Employee() called for Susan Baker
~Employee() called for Robert Jones
Number of employees after objects are deleted is 0




Software Engineering Observation 10.12








Some
organizations specify in their software engineering standards that all calls to
static member functions be made using the
class name rather than an object
handle.



A member function should be declared
static if it does not access non-static data members or
non-static member functions of the class. Unlike non-static
member functions, a static member function does not have a
this pointer, because static data members and static
member functions exist independently of any objects of a class. The
this pointer must refer to a specific object
of the class, and when a static member
function is called, there might not be any objects of its class in memory.



Common Programming Error 10.11








Using the this pointer in a
static member function is a compilation
error.




Common Programming Error 10.12








Declaring a static member
function const is a compilation error. The
const qualifier indicates that a function cannot
modify the contents of the object in which it operates, but static member functions exist and operate independently of any
objects of the class.



Lines 18–19 of Fig. 10.23 use operator new
to dynamically allocate two Employee objects.
Remember that the program will terminate immediately if it is unable to allocate
one or both of these objects. When each Employee object is allocated,
its constructor is called. When delete is used in lines
30 and 32 to deallocate the Employee objects, each object's destructor
is called.



Error-Prevention Tip 10.2








After deleting
dynamically allocated memory, set the pointer that referred to that memory to
0. This disconnects the pointer from the
previously allocated space on the free store. This space in memory could still
contain information, despite having been deleted. By setting the pointer to
0, the program loses any access to that
free-store space, which, in fact, could have already been reallocated for a
different purpose. If you didn't set the pointer to 0, your code could inadvertently access this new
information, causing extremely subtle, nonrepeatable logic
errors.





 


Creating the Object











Creating the Object


With a better understanding of the XHR and different request models, we can now focus on creating the object. Creating the request object is trivial in comparison to the power that is unleashed when applying it to a project.


To create the request object, you must check to see if the browser uses the XHR or the ActiveX object. The primary difference between the objects is the browsers that use them. Windows Internet Explorer (IE) 5 and above use the ActiveX object, whereas Mozilla, Firefox, Netscape, Opera, and Safari use the native JavaScript XHR object. The second difference is the way in which we create each object: Windows IE requires the name of the object to be passed as a parameter to the ActiveX constructor, whereas the other browsers provide us with the native JavaScript object, which only we need to instantiate:


function makeRequest(url)
{
if(window.XMLHttpRequest)
{
request = new XMLHttpRequest();
}
else if(window.ActiveXObject)
{
request = new ActiveXObject("Msxml2.XMLHTTP");
}

sendRequest(url);
}


As you can see from the code sample, the object creation is really a very simple task. We create a method named makeRequest to handleyou guessed itmaking the request and to decipher what type of object the browser uses by creating a condition that checks for the native XHR object. If this object is not available, we check for the ActiveXObject. After the correct object type has been identified for the current browser, the correct object is instantiated and a request object is created. This object can now be used to access all the properties and methods listed in Tables 2.1 and 2.2, which are available to the XHR object.


Table 2.1. A List of XHR Properties and Corresponding Definitions

Properties

Definitions

onreadystatechange

An event handler that fires when the state of the request object changes.

readyState

Returns number values that indicate the current state of the object. These values are listed in Table 2.3.

responseText

String version of the response from the server.

responseXML

DOM-compatible document object of the response from the server.

status

Status code of the response from the server.

statusText

A status message returned as a string.




Table 2.2. A List of XHR Methods and Corresponding Definitions

Methods

Definitions

Abort()

Cancels the current HTTP request.

getAllResponseHeaders()

Retrieves the values of all the HTTP headers.

geTResponseHeader("label")

Retrieves the value of a specified HTTP header from the response body.

Open("method", "URL"[, asyncFlag[, "userName"[, "password"]]])

Initializes an MSXML2.XMLHTTP or Microsoft.XMLHTTP request, and specifies the method, URL, and authentication information for the request.

Send(content)

Sends an HTTP request to the server and receives a response.

SetRequestHeader("label", "value")

Specifies the value of an HTTP header based on the label.



These two tables might look like they have only a small number of options, but as you will find in the following chapters, they pack a lot of power when used with back-end code, a database, and a dynamic front end.












Chapter 2. Metaphors for a Richer Understanding of Software Development










 < Free Open Study > 







Chapter 2. Metaphors for a Richer Understanding of Software Development



cc2e.com/0278



Contents



  • The Importance of Metaphors page 9

  • How to Use Software Metaphors page 11

  • Common Software Metaphors page 13



Related Topic



  • Heuristics in design: "Design Is a Heuristic Process" in Section 5.1



Computer science has some of the most colorful language of any field. In what other field can you walk into a sterile room, carefully controlled at 68°F, and find viruses, Trojan horses, worms, bugs, bombs, crashes, flames, twisted sex changers, and fatal errors?



These graphic metaphors describe specific software phenomena. Equally vivid metaphors describe broader phenomena, and you can use them to improve your understanding of the software-development process.



The rest of the book doesn't directly depend on the discussion of metaphors in this chapter. Skip it if you want to get to the practical suggestions. Read it if you want to think about software development more clearly.














     < Free Open Study > 



    Specifying a Server Configuration









    Specifying a Server Configuration


    JBoss is not only powerful, it's also very configurable. At the center of JBoss is a JMX microkernel that manages the MBeans that control the various services in the server. The JBoss documentation describes the microkernel as a spine, and the analogy fits well. JBoss in its minimum configuration is like the brainstem sitting at the top of the spine. It provides all the core autonomic features (to steal some thunder from IBM's autonomic computing initiative). The spinal column is the attachment point for all the MBeans that want to talk to the brainstem and to each other over the spinal cord. This architecture provides a strong backbone to build on, but at the same time it can work at its most minimal state and still do amazing things. It makes JBoss useful for everything from very small, embedded applications all the way up to full-blown enterprise server applications, all on the same spinal column.


    Having a flexible architecture is pointless unless you can make use of that flexibility. JBoss provides several different configurations that range from a barebones server with no J2EE capabilities to a superserver with more services than any single application could possibly use. Figure 1-4 shows the three standard server configurations.



    Developer's Notebook 1-4. Server configurations



    Here are descriptions of the server configurations:



    Note: There is a huge difference in the size of these configurations. The minimal configuration can start up in just a few seconds, whereas it can take nearly a minute for the all configuration to completely start on a typical developer machine.


    minimal


    This configuration provides the bare services you might need in the simplest application: logging, JNDI naming services, and URL deployment scanning. You'd use this setup in tight memory situations, or when you want to use JMX to control exactly which MBeans are loaded, and when and how they are loaded. This configuration doesn't provide support for EJBs, web applications, JMS, or any other high-level services. It's just a raw microkernel waiting to be molded.


    default


    This is a lightweight J2EE configuration; it's the one most people use. It provides most of the common J2EE services, but it doesn't include IIOP, JAXR, or clustering services, for example. These services can be added easily to this configuration.


    all


    This configuration includes everything in JBoss. If you need any extra services not provided in the default configuration, this is the place to start. It's often easier to start from the all configuration and take out unwanted services than to start with the default configuration and add the desired services.



    Note: The all configuration includes everthing but proverbial kitchen sink.



    How do I do that?


    You specify a particular configuration using the -c command to the run script. To run the minimal configuration, for example, use -c minimal.


    Here is an example:



    Note: You can also use --configuration = minimal, if you prefer to use long configuration names.


    [bin]$ ./run.sh -c minimal











      18.12. String Stream Processing



      18.12. String Stream Processing


      In addition to standard stream I/O
      and file stream I/O, C++ stream I/O includes capabilities for inputting from,
      and outputting to, strings in memory.
      These capabilities often are referred to as in-memory I/O or string stream processing.


      Input from a string is supported by
      class istringstream.
      Output to a string is supported by class ostringstream. The class names
      istringstream and ostringstream are actually aliases defined
      by the typedefs


      typedef basic_istringstream< char > istringstream;
      typedef basic_ostringstream< char > ostringstream;


      Class templates basic_istringstream and
      basic_ostringstream provide the same functionality as classes
      istream and ostream plus other
      member functions specific to in-memory formatting. Programs that use in-memory
      formatting must include the <sstream> and
      <iostream> header files.


      One application of these techniques is
      data validation. A program can read an entire line at a time from the input
      stream into a string. Next, a validation
      routine can scrutinize the contents of the string and correct (or repair) the data, if necessary. Then the
      program can proceed to input from the string, knowing that the input data is in the proper
      format.


      Outputting to a string is a nice
      way to take advantage of the powerful output formatting capabilities of C++
      streams. Data can be prepared in a string
      to mimic the edited screen format. That string
      could be written to a disk file to preserve the screen image.


      An ostringstream object uses a string object to store the output data. The str member function of class
      ostringstream returns a copy of that string.


      Figure 18.11 demonstrates an
      ostringstream object. The program creates ostringstream object
      outputString (line 15) and uses the stream insertion
      operator to output a series of strings and numerical values to the
      object.













      Fig. 18.11. Using a dynamically allocated
      ostringstream object.


       

       1   // Fig. 18.11: Fig18_11.cpp
      2 // Using a dynamically allocated ostringstream object.
      3 #include <iostream>
      4 using std::cout;
      5 using std::endl;
      6
      7 #include <string>
      8 using std::string;
      9
      10 #include <sstream> // header file for string stream processing
      11 using std::ostringstream; // stream insertion operators
      12
      13 int main()
      14 {
      15 ostringstream outputString; // create ostringstream instance
      16
      17 string string1( "Output of several data types " );
      18 string string2( "to an ostringstream object:" );
      19 string string3( "\n double: " );
      20 string string4( "\n int: " );
      21 string string5( "\naddress of int: " );
      22
      23 double double1 = 123.4567;
      24 int integer = 22;
      25
      26 // output strings, double and int to ostringstream outputString
      27 outputString << string1 << string2 << string3 << double1
      28 << string4 << integer << string5 << &integer;
      29
      30 // call str to obtain string contents of the ostringstream
      31 cout << "outputString contains:\n" << outputString.str();
      32
      33 // add additional characters and call str to output string
      34 outputString << "\nmore characters added";
      35 cout << "\n\nafter additional stream insertions,\n"
      36 << "outputString contains:\n" << outputString.str() << endl;
      37 return 0;
      38 } // end main



      outputString contains:
      Output of several data types to an ostringstream object:
      double: 123.457
      int: 22
      address of int: 0012F540

      after additional stream insertions,
      outputString contains:
      Output of several data types to an ostringstream object:
      double: 123.457
      int: 22
      address of int: 0012F540
      more characters added



      Lines 27–28 output string string1, string
      string2
      , string string3, double double1, string
      string4
      , int integer, string string5 and the address of
      int integer—all to outputString in
      memory. Line 31 uses the stream insertion operator and the call
      outputString.str() to display a copy of the
      string created in lines 27–28. Line 34
      demonstrates that more data can be appended to the string in memory by simply issuing another stream insertion
      operation to outputString. Lines 35–36 display string
      outputString
      after appending additional characters.


      An istringstream object inputs
      data from a string in memory to program
      variables. Data is stored in an istringstream
      object as characters. Input from the istringstream object works identically to input from any file. The end of
      the string is interpreted by the istringstream object as
      end-of-file.


      Figure
      18.12 demonstrates input from an istringstream object. Lines 15–16
      create string input containing the data and istringstream
      object inputString constructed to contain the data in string
      input
      . The string input contains the data


      Input test 123 4.7 A


      which, when read as input to the program, consist of two
      strings ("Input" and "test"), an int (123),
      a double (4.7) and a char ('A'). These
      characters are extracted to variables string1, string2,
      integer, double1 and character in line 23.













      Fig. 18.12. Demonstrating input from an
      istringstream object.


       

       1   // Fig. 18.12: Fig18_12.cpp
      2 // Demonstrating input from an istringstream object.
      3 #include <iostream>
      4 using std::cout;
      5 using std::endl;
      6
      7 #include <string>
      8 using std::string;
      9
      10 #include <sstream>
      11 using std::istringstream;
      12
      13 int main()
      14 {
      15 string input( "Input test 123 4.7 A" );
      16 istringstream inputString( input );
      17 string string1;
      18 string string2;
      19 int integer;
      20 double double1;
      21 char character;
      22
      23 inputString >> string1 >> string2 >> integer >> double1 >> character;
      24
      25 cout << "The following items were extracted\n"
      26 << "from the istringstream object:" << "\nstring: " << string1
      27 << "\nstring: " << string2 << "\n int: " << integer
      28 << "\ndouble: " << double1 << "\n char: " << character;
      29
      30 // attempt to read from empty stream
      31 long value;
      32 inputString >> value;
      33
      34 // test stream results
      35 if ( inputString.good() )
      36 cout << "\n\nlong value is: " << value << endl;
      37 else
      38 cout << "\n\ninputString is empty" << endl;
      39
      40 return 0;
      41 } // end main



      The following items were extracted
      from the istringstream object:
      string: Input
      string: test
      int: 123
      double: 4.7
      char: A

      inputString is empty



      The data is then output in lines 25–28.
      The program attempts to read from inputString again in line 32. The
      if condition in line 35 uses function good (Section
      15.8) to test if any data remains. Because no
      data remains, the function returns false and the else part of
      the if...else statement is executed.


       


      Virtual Hosting with Tomcat 4.x





























      Chapter 19 -
      Shared Tomcat Hosting
      byVivek Chopra, Ben Galbraithet al.
      Wrox Press 2003































      Virtual Hosting with Tomcat 4.x


      Virtual host configuration in Tomcat 4.x is different from that in Tomcat 3.x in a number of ways. Mostly, this has been because Tomcat 4.x was rewritten from scratch and the whole architecture of the various components of Tomcat has changed.


      One common factor between the two configurations is that the configuration in Apache for transferring requests for servlets and JSP pages to the Tomcat process has more or less remained the same while using the mod_jk adapter. In addition, Tomcat 4.x can also use mod_webapp, which we will see later.


      We first take a look at configuring Tomcat 4.x as a standalone server implementing virtual hosts.




      Tomcat 4.x As a Standalone Server


      Virtual host definitions in Tomcat 4.x have to be provided in the server configuration file, server.xml itself. Once these additions have been done, we simply need to restart Tomcat to use the virtual host definitions.



      Editing server.xml


      The default sample server.xml file of the Tomcat 4.x build contains two services - one for the standalone server, and one for the server that cooperates with an Apache web server using the WARP protocol. We can remove the definition for the second service and reuse that of the first service.


      The top-level <Service> element for the standalone server would look like the following:





      <Server port="8005" shutdown="SHUTDOWN" debug="0">
      <Service name="Tomcat-Standalone">

      </Service>
      </Server>


      The rest of the configuration would go inside this <Service> container element. We then add the Connectors to be used for this service. Since this is a standalone server, we only need the HTTP/1.1 connector to communicate with the outside world.


      We add the following Connector definition inside the <Service> element.




      <Connector className="org.apache.catalina.connector.http.HttpConnector"
      port="8080"
      minProcessors="5"
      maxProcessors="75"
      enableLookups="true"
      acceptCount="10"
      debug="0"
      connectionTimeout="60000"/>


      This sets up Tomcat to listen to port 8080 for incoming web requests.


      We now add the <Engine> element to the <Service> by adding the following lines just after the <Connector> element and inside the <Service> element:




      <Engine name="Standalone" defaultHost="europa.dom" debug="0">

      </Engine>


      This specifies an engine for the service that processes incoming requests from the connectors. After any request has been received by the connector and passed on to the engine, the engine would then take a look at the HTTP headers, especially the Host:tag, and determine which of the virtual host definitions that it handles would receive the request. If none of the virtual host seems to match the request headers, the engine passes on the request to a default host. The name of the default virtual host is specified in the attribute defaultHost. The value of this attribute must match a <Host> definition in the engine.


      For our purposes, we see that the virtual host definition of europa.dom is served by default when a web request is made using the IP address (instead of a host name).


      We next add the virtual host definition of europa.dom to the engine. We add the following content inside the <Engine> element:




      <Host name="europa.dom" debug="0"
      appBase="/home/sites/europa.dom/webapps"
      unpackWARs="true">
      </Host>



      This defines a virtual host entry for europa.dom in Tomcat. We further add some logging functionality to this virtual host by placing the following content within the <Host> element:




      <Valve className="org.apache.catalina.valves.AccessLogValve"
      directory="/home/sites/europa.dom/logs"
      prefix="europa_access."
      suffix=".log"
      pattern="common" />

      <Logger className="org.apache.catalina.logger.FileLogger"
      directory="/home/sites/europa.dom/logs"
      prefix="europa_catalina."
      suffix=".log"
      timestamp="true"/>


      This defines two logging services for this virtual host. The <Logger> element is covered in Chapter 5.


      We now add the contexts to serve for this virtual host, inside the <Host> element.




      <Context path="" docBase="ROOT" debug="0"/>
      <Context path="/shop" docBase="shop" debug="0" />


      We have added two contexts here. The first one is the default context with an empty context path. This context either has to be defined explicitly or provided automatically by Tomcat (that is, without you defining it here) if you have a web application called ROOT in the appBase of the virtual host.


      As an example of how new web applications other than the default one have to be added to the site, we have also added the web application called shop which uses the /shop context path.


      One nice thing about context definitions in Tomcat 4.x is that it provides automatic contexts in case you haven't defined them in the host definition. To provide this functionality, Tomcat looks at directories inside the appBase directory. If these directories follow the web application structure, specifically if they contain a WEB-INF/web.xml file in them, Tomcat automatically provides contexts with context paths equal to the name of the directory under appBase.




      Remember that the default parameters of these contexts are picked up from $CATALINA_HOME/conf/web.xml.



      If you need to override some global parameters to these contexts, you need to place them within the <Context></Context> elements. Examples would be, logging for this context in a separate file, context parameters, resource definitions, and so on.


      This completes the virtual host definition for europa.dom. For the virtual host callisto.dom, we add another virtual host entry similar to that of europa.dom:


      We save this file as $CATALINA_HOME/conf/server.xml and restart the Tomcat service.


      We first check the test JSP file in the europa.dom virtual host using the URL http://europa.dom:8080/test.jsp:






      We then check out the callisto.dom using the URL http://callisto.dom:8080/test.jsp:





      We then check whether the default host setting of the <Engine> element is working properly. For this we use a host name other than the ones specified explicitly as <Host> definitions. Let's try accessing using the IP address 10.0.0.1:





      As we can see, Tomcat serves the contents of the europa.dom virtual host, as defined in the default virtual host entry of the engine.


      Now that we have Tomcat 4.0 working as a standalone server for the virtual hosts, let's make it work along with Apache.








      Tomcat 4.0 with Apache


      As we saw in the Tomcat 3.3 Apache configuration section, Apache communicates with Tomcat using connectors like mod_jk. In Tomcat 4.x, support for another kind of adapter called mod_webapp has also been added. While we will concentrate on using mod_jk in this section, as it is considered to be more stable than the new mod_webapp, we will touch mod_webapp briefly at the end.


      We can use the same mod_jk connector in both versions of Tomcat (it's the same at the Apache end after all). However, if you rely on Tomcat to automatically generate the mod_jk.conf file, the procedure for Tomcat 4.x is a bit different to the one in Tomcat 3.3 and will be explained shortly.



      Tomcat 4.0 with the AJP 1.3 Connector


      To use Tomcat along with the AJP connectors of Apache, we need to modify the previously used server.xml file.



      First of all we change the name of the <Service> element to reflect the fact that Tomcat is now working along with Apache. While this is not strictly necessary, it helps in distinguishing between multiple configuration files of Tomcat lying in your machine:




      [...]
      <Service name="Tomcat-Apache-mod_jk">
      [...]


      We then replace the HTTP/1.1 <Connector> definition with the given AJP1.3 <Connector> definition instead.




      The HTTP connector could be left in place if you still want Tomcat to handle web requests directly at port 8080.





      <Connector className="org.apache.ajp.tomcat4.Ajp13Connector"
      port="8009"
      minProcessors="5"
      maxProcessors="75"
      acceptCount="10"
      debug="0"/>


      That is all we have to do to configure Tomcat to communicate with the Apache mod_jk adapter. However, to make our configuration of Apache easier, we can ask Tomcat to auto-generate the required Apache configuration files.





      Auto Generating modjk.conf


      To auto-generate the Apache configuration files, we have to add appropriate <ApacheConfig> listeners at various points in the server.xml file.


      We start by generating the global mod_jk configuration, which is done by adding the following just after the <Service> opening element in the server.xml file:




      <Listener className="org.apache.ajp.tomcat4.config.ApacheConfig"
      modJk="/usr/local/apache/libexec/mod_jk.so"
      jkDebug="info"
      workersConfig="/usr/local/tomcat/conf/jk/workers.properties"
      jkLog="/usr/local/tomcat/logs/mod_jk.log"
      noRoot="false"
      forwardAll="false" />


      You would notice the familiar noRoot and forwardAll attributes from the <ApacheConfig> element of Tomcat 3.3. They have similar functions here:




      • The modJk attribute contains the path to the mod_jk connector module for Apache. This path would be used in the auto-generated output to inform Apache where to load the module from.





      • jkDebug attribute indicates the level of logging which could be one of debug, info, error, or emerg. Not setting this attribute would cause logging to be disabled.





      • workersConfig contains the path to the worker.properties file that needs to be mentioned in the generated file for the connector to find the workers to send the request to. jkLog contains the path to the file to be used for logging.





      To generate the <VirtualHost> sections we add the following just after the opening element of all the <Host> entries.




      <Listener className="org.apache.ajp.tomcat4.config.ApacheConfig"
      append="true" />


      The final server.xml file should look like this:




      <Server port="8005" shutdown="SHUTDOWN" debug="0">
      <Service name="Tomcat-Apache-modjk">
      <Listener className="org.apache.ajp.tomcat4.config.ApacheConfig"
      modJk="/usr/local/apache/libexec/mod_jk.so"
      jkDebug="info"
      workersConfig="/usr/local/tomcat/build/conf/jk/workers.properties"
      jkLog="/usr/local/tomcat/logs/mod_jk.log"
      noRoot="false"
      forwardAll="false" />

      <Connector className="org.apache.ajp.tomcat4.Ajp13Connector"
      port="8009"
      minProcessors="5"
      maxProcessors="75"
      acceptCount="10"
      debug="0"/>

      <Engine name="Standalone" defaultHost="europa.dom" debug="0">

      <!-- Virtual host definition for europa.dom -->
      <Host name="europa.dom" debug="0"
      appBase="/home/sites/europa.dom/webapps"
      unpackWARs="true">
      <Listener className="org.apache.ajp.tomcat4.config.ApacheConfig"
      append="true" />
      <Valve className="org.apache.catalina.valves.AccessLogValve"
      directory="/home/sites/europa.dom/logs"
      prefix="europa_access."
      suffix=".log"
      pattern="common"/>

      <Logger className="org.apache.catalina.logger.FileLogger"
      directory="/home/sites/europa.dom/logs"
      prefix="europa_catalina."
      suffix=".log"
      timestamp="true"/>
      <Context path="" docBase="ROOT" debug="0"/>

      <Context path="/shop" docBase="shop" debug="0" />

      </Host>

      <!-- Virtual host definition for callisto.dom -->
      <Host name="callisto.dom"
      debug="0"
      appBase="/home/sites/callisto.dom/webapps"
      unpackWARs="true">
      <Listener className="org.apache.ajp.tomcat4.config.ApacheConfig"
      append="true" />

      <Valve className="org.apache.catalina.valves.AccessLogValve"
      directory="/home/sites/callisto.dom/logs"
      prefix="callisto_access."
      suffix=".log"
      pattern="common"/>

      <Logger className="org.apache.catalina.logger.FileLogger"
      directory="/home/sites/callisto.dom/logs"
      prefix="callisto_catalina."
      suffix=".log"
      timestamp="true"/>
      <Context path="" docBase="ROOT" debug="0"/>

      <Context path="/shop" docBase="shop" debug="0" />

      </Host>
      </Engine>
      </Service>
      </Server>


      Restart Tomcat to get the auto-generated mod_jk.conf file. It will be in $CATALINA_HOME/conf/auto.


      While we can use the auto-generated files directly in our Apache configuration with the Include directive, we should remember that this time Tomcat generates these files every time it starts. So any changes made to this file will be lost. In cases where you would like to make any custom changes to the virtual host definitions, for example, adding the Apache ACL to certain file locations, we should instead write this configuration file by hand. This is in contrast to Tomcat 3.3, which generates the file only when asked to do so. Of course, in Tomcat 4.0.x, once you have got the generated mod_jk.conf, you can always remove the ApacheConfig <Listener> elements from the server.xml file. This way when Tomcat restarts it won't regenerate the file.


      In either case, the final mod_jk.conf file should look like the following:




      <IfModule !mod_jk.c>
      LoadModule jk_module /usr/local/apache/libexec/mod_jk.so
      </IfModule>

      JkWorkersFile "/usr/local/tomcat/build/conf/jk/workers.properties"
      JkLogFile "/usr/local/tomcat/build/logs/mod_jk.log"

      JkLogLevel emerg

      NameVirtualHost *

      <VirtualHost *>
      ServerName europa.dom
      DocumentRoot "/home/sites/europa.dom/webapps/ROOT"
      <Directory "/home/sites/europa.dom/webapps/ROOT">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      JkMount /servlet ajp13
      JkMount /servlet/* ajp13
      JkMount /*.jsp ajp13
      JkMount /shop ajp13
      JkMount /shop/* ajp13

      Alias /shop "/home/sites/europa.dom/webapps/shop"

      <Directory "/home/sites/europa.dom/webapps/shop">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/shop/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/shop/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      JkMount /shop/servlet ajp13
      JkMount /shop/servlet/* ajp13
      JkMount /shop/*.jsp ajp13
      </VirtualHost>

      <VirtualHost *>
      ServerName callisto.dom

      DocumentRoot "/home/sites/callisto.dom/webapps/ROOT"
      <Directory "/home/sites/callisto.dom/webapps/ROOT">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      JkMount /servlet ajp13
      JkMount /servlet/* ajp13
      JkMount /*.jsp ajp13

      Alias /shop "/home/sites/callisto.dom/webapps/shop"

      <Directory "/home/sites/callisto.dom/webapps/shop">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/shop/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/shop/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      JkMount /shop/servlet ajp13
      JkMount /shop/servlet/* ajp13
      JkMount /shop/*.jsp ajp13
      </VirtualHost>


      Restart Tomcat and Apache and check the test files for the virtual domains again.





      Tomcat 4.0 with mod_webapp


      Setting up the new Apache-Tomcat mod_webapp connector is not much different from setting up the mod_jk connector, with the important difference that Tomcat does not have a module like <ApacheConfig> for automatic generation of mod_webapp Apache configuration files. Even though Tomcat's automatic generation of mod_jk.conf is convenient, commercial shared hosting providers won't rely on it too much, because of the myriad different directives that they would like to put in the <VirtualHost> entry. They would rather have their own automated scripts to generate the mod_jk.conf files with their additions. Given that, the syntax for writing a mod_jk configuration and a mod_webapp configuration is not much different


      If you want a comparison between the two connectors, see Chapter 11.




      Configuring Apache

      To configure Apache for mod_webapp, instead of the mod_jk-specific commands in the Apache configuration file, we add the mod_webapp directives. Instead of the LoadModule and AddModule directives of mod_jk, we add:




      LoadModule webapp_module libexec/mod_webapp.so
      AddModule mod_webapp.c


      Also, replace the other global Jk* directives with:




      WebAppConnection tomcat_warp warp localhost:8008


      This defines a WARP connection with the name tomcat_warp for communicating with the Tomcat server on the same host at port 8008. We now replace all the JkMount directives with WebAppDeploy directives. WebAppDeploy directives are of the form:




      WebAppDeploy <webappname> <warpconnection> <contextpath>


      Thus the new Apache configuration file, which we would name as mod_webapp.conf, would look like this:




      LoadModule webapp_module libexec/mod_webapp.so
      AddModule mod_webapp.c

      WebAppConnection tomcat_warp warp localhost:8008

      NameVirtualHost *

      <VirtualHost *>
      ServerName europa.dom

      WebAppDeploy ROOT tomcat_warp /
      WebAppDeploy shop tomcat_warp /shop

      DocumentRoot "/home/sites/europa.dom/webapps/ROOT"

      <Directory "/home/sites/europa.dom/webapps/ROOT">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      Alias /shop "/home/sites/europa.dom/webapps/shop"
      <Directory "/home/sites/europa.dom/webapps/shop">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/shop/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/shop/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      </VirtualHost>

      <VirtualHost *>
      ServerName callisto.dom

      WebAppDeploy ROOT tomcat_warp /
      WebAppDeploy shop tomcat_warp /shop

      DocumentRoot "/home/sites/callisto.dom/webapps/ROOT"

      <Directory "/home/sites/callisto.dom/webapps/ROOT">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      Alias /shop "/home/sites/callisto.dom/webapps/shop"

      <Directory "/home/sites/callisto.dom/webapps/shop">
      Options Indexes FollowSymLinks
      DirectoryIndex index.jsp index.html index.htm
      </Directory>

      <Location "/shop/WEB-INF/*">
      AllowOverride None
      deny from all
      </Location>

      <Location "/shop/META-INF/*">
      AllowOverride None
      deny from all
      </Location>

      </VirtualHost>


      Note how the default context is sent to the ROOT webapp. Another point to be noted is that in this configuration, Tomcat would serve even the static files of the web application. Also, when Tomcat serves the default context of the virtual host through WARP protocol, the <Directory> directives are ignored, and hence the <Directory> directives given above are strictly redundant. However, the <Location> directives are still processed by Apache. These are in a way redundant too. Without these <Location> directives, any requests of files in META-INF and WEB-INF directories would be passed by mod_webapp to Tomcat, which according to the Servlet API specification would reject such requests. Having the <Location> ACL directives in the Apache configuration actually stops such requests before they are passed to Tomcat.


      This configuration file should be added to the Apache configuration using the Include directive, replacing the previous mod_jk configuration.





      Configuring Tomcat 4.x

      For configuring Tomcat to receive requests only through the WARP protocol of mod_webapp, we need to modify the server.xml file. In the last used server.xml, we have to replace the AJP <Connector> elements with WARP <Connector> ones. Also, since <ApacheConfig> can't handle WARP-based configurations, we remove all the <Listener> elements previously used to auto-generate Apache configuration files. The final server.xml file would look like this.




      <Server port="8005" shutdown="SHUTDOWN" debug="0">
      <Service name="Tomcat-Apache-warp">

      <Connector className="org.apache.catalina.connector.warp.WarpConnector"
      port="8008" minProcessors="5" maxProcessors="75"
      acceptCount="10" debug="0"/>

      <Engine className="org.apache.catalina.connector.warp.WarpEngine"
      name="Apache" defaultHost="europa.dom" debug="0">

      <!-- Virtual host definition for europa.dom -->
      <Host name="europa.dom" debug="0"
      appBase="/home/sites/europa.dom/webapps"
      unpackWARs="true">
      <Valve className="org.apache.catalina.valves.AccessLogValve"
      directory="/home/sites/europa.dom/logs"
      prefix="europa_access."
      suffix=".log"
      pattern="common"/>

      <Logger className="org.apache.catalina.logger.FileLogger"
      directory="/home/sites/europa.dom/logs"
      prefix="europa_catalina."
      suffix=".log"
      timestamp="true"/>
      <Context path="" docBase="ROOT" debug="0"/>

      <Context path="/shop" docBase="shop" debug="0" />

      </Host>

      <!-- Virtual host definition for callisto.dom -->
      <Host name="callisto.dom" debug="0"
      appBase="/home/sites/callisto.dom/webapps"
      unpackWARs="true">

      <Valve className="org.apache.catalina.valves.AccessLogValve"
      directory="/home/sites/callisto.dom/logs"
      prefix="callisto_access."
      suffix=".log"
      pattern="common"/>

      <Logger className="org.apache.catalina.logger.FileLogger"
      directory="/home/sites/callisto.dom/logs"
      prefix="callisto_catalina."
      suffix=".log"
      timestamp="true"/>

      <Context path="" docBase="ROOT" debug="0"/>

      <Context path="/shop" docBase="shop" debug="0" />

      </Host>
      </Engine>
      </Service>
      </Server>


      We should now restart Apache and Tomcat and check the functioning of the two virtual hosts.