Thursday, December 13, 2007

Serializable connection?

JDBC DataSource is the evolution of classic JDBC DriverManager. It is an entity that doles out database connections.
1) automatically pool and reuse database connection
2) DataSource objects can be stored in JNDI tree. i.e., it is serializable. How can we serialize a connection? The secret is that it does RMI-based proxy for JDBC connections. The actual connections are in the server JVM, but RMI connection (stub object) is sent to external clients, which do their JDBC calls, which ferry to the connection and ferry back results.
*** The RMI stub object is network aware reference to remote object and it is serializable.

Thursday, December 6, 2007

Spring AOP & Robotic Spiders

- Definition
# Advice
It contains the logic of your aspect. Also because Spring's jointPoint model is built around method interception. There are four types of advice types in Spring: Around, Before, After, Throws.

# PointCut
It defines where advices is woven into classes. It determines if a particular method on a particular class mataches a particular criterion. There are static and dynamic pointcut. Spring provides two static pointcuts: StaticMethodMatcherPointcut and RegexpMethodPointcut.

It seems a good practice to use annotation to identify the pointcuts and achieve a good balance between xml config and annotation, e.g.,
  public boolean matches(Method method, Class targetClass) {
     boolean isAnnotationPresent = method.isAnnotationPresent(Auditable.class);

# Advisor
It combine advice and pointcuts into one object. Most of Spring pointcuts have a corresponding pointcutAdvisor such as StaticMethodMatcherPointcutAdvisor.

public interface PointcutAdvisor {
  Pointcut getPointcut();
  Advice getAdvice();

- The ProxyFactoryBean class is a central class for explicitly creating proxied
objects within a BeanFactory. As demonstrated, you can give it an interface to
implement, a target object to proxy, and advice to weave in, and it will create a
brand-new proxied object. (dynamically create proxy class)

<bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean" >
   <property name="proxyInterfaces">
   <property name="interceptorNames">
   <property name="target"><ref bean="kwikEMartTarget"/>

The interceptorNames is the bean names of the advice to be applied to the target. It can be names of interceptors, advisors, or any other advice type.

The proxied objects will be created when it loads all of the beans from the BeanFactory on runtime. (aspect weaving could happen on compile time or class load time too which requires special compiler and classloader).
*** It is interesting to know that webwork (Struts2) interceptor uses different mechanism though they shares the same AOP concepts. It doesn't create proxy objects (classes) or modify byte code. It applies command pattern to decouple the caller and called action, ActionInvocation keeps a stack of interceptor, and it calls intercept() one by one until it reach the execute() of action instance. After result is return, the interceptors is invoked in reverse order. The magic exists in actionInvocation.invoke() which is a recursive call.

- Auto Proxy
For large application, it become cumbersome to explicitly create each proxy object using the ProxyFactoryBean. BeanNameAutoProxyCreator create proxies for beans that match a set of names. The more powerful autoproxy creator is the DefaultAdvisorAutoProxyCreator. The magic of it lies within its implementation of BeanPostProcessor interface. After you bean's definition have been read in by the ApplicationContext, the DefaultAdvisorAutoProxyCreator scours the context for any advisors. It then applied these advisors to any beans that match the advisor's pointcut. - It is like robotic spiders where unleashed to find Tom Cruise in Minority Report.

<bean id="performanceThreasholdInterceptor" ...>...</bean>
<bean id="advisor" class="...RegexpMethodPointcutAdvisor">
  <property name="advice">
    <bean class="performanceThresholdInterceptor"/>
  <property name="pattern">
<bean id="autoProxyCreator" class=".....DefaultAdvisorAutoProxyCreator"/>

Tuesday, December 4, 2007

CSS table layout

div.row {
clear: both;
padding-top: 5px;

div.row span.label {
float: left;
width: 100px;
text-align: right;

div.row span.formw {
float: right;
width: 235px;
text-align: left;

Span is inline element, and text-align can only be used inside a block element to align its content. So why can we use it here? The secret is that with 'float' defined, span is escalated to block level element, as you implicitly added a "display: block" to it.

Java File IO

“Bridge/Filter” classes: InputStreamReader (<-FileReader) converts an InputStream to a Reader and OutputStreamWriter (<-FileWriter) converts an OutputStream to a Writer.

- Two basic file format: binary vs ASCII
For example, integer 268
In binary representation, recall that in Java, integers are stored in 2's complement using 4 bytes of storage. Thus, 268 has the internal representation: 00000000 00000000 00000001 00001100
In ASCII representation, which would mean saving the ASCII code for the digits "2", "6", and "8". The code is

"2" = 50(10) = 00110010(2)
"6" = 54(10) = 00110110(2)
"8" = 56(10) = 00111000(2)

Thus, 268 would be written to the file as
00110010 00110110 00111000

The advantages of ACII Format
* ASCII text can be understood by any text editor.
* The file can be edited by hand if you wish to change information.
The disadvantages of ASCII Format
* Storing in ASCII requires first converting the number to ASCII
* In the above example, the ASCII version took less space (3 bytes vs 4 bytes). However, more typically, ASCII takes up more space and so your file will be bigger. For example, the number 10245 uses 5 bytes in ASCII rather than 4 in binary.
* ASCII only allows for 256 characters.

- Writing to an ASCII file using PrintWriter

File myFile = new File("DataFiles\\stuff.dat");

// Create an Output Stream
FileOutputStream outStream = new FileOutputStream(myFile);

// Filter bytes to ASCII
PrintWriter out = new PrintWriter(outStream);

// Here we actually write to file
out.println("Hello, this is a test.");

// Reading from an ASCII file using BufferedReader
File myFile = new File("DataFiles\\stuff.dat");
// Create a Character Input Stream
// Note: FileReader inherits from InputStreamReader, which is a bridge from byte
// streams to character streams
FileReader inStream = new FileReader(myFile)

// Filter the Input Stream - buffers characters for efficiency
BufferedReader in = new BufferedReader(inStream);

String first = in.readLine();
String second = in.readLine();
if (second == null) System.out.println("End of file reached.");

Thinking in Java / IO
File Input and Output

Friday, November 16, 2007

Javascript in depth

function User (name, age) {;

/* User is a reference to the constructor */
User.prototype.getName = function() { ...}
// add new function to object prototype
User.prototype = new Person();
// inherit all of Person object's method, in JS, we inherit from physical object;
//it is like that you get all properties/methods when creating a new Person object
User.cloneUser = function(user) { ...} // static method

var user = new User("Bob", 33);

- what is constructor?
It is same as other function, but is invoked using keyword 'new'. Two things happen when invoke using 'new'
a) the context is switched to the created object, i.e., 'this' refers to object user. Otherwise, if invoke without new, this variable refers to root object window.
b) The constructor's prototype property (the public accessible one) is assigned to the new instance's private prototype.
In a nutshell, all JS objects have a private prototype property. I say "private" because this property is not directly visible or accessible from the object itself (but Visible for constructor!!). When the runtime performs a lookup on a property, it first looks at the instance to see if that property is defined there. If it is not defined there, the runtime looks for the property on the object's private prototype. Prototypes can have their own private prototype, so the search can keep working its way up the prototype chain.

- HTML DOM load
The order of loading is:
1. HTML is parsed
2. External scripts/style sheets (include the one in header) are loaded
3. Scripts are executed as they are parsed in the document (** significant problem: the scripts in these two places won't have accessed to DOM.)
4. HTML DOM is fully constructed. (inline scripts are executed as they are encountered. DOM is partially constructed atm)
5. Images and external content are loaded
6. The page is finished loading.

- Boolean operators
In JS, a||b <=> a?a:b a&&b <=> a?b:a they are not necessary return true or false as in Java/C. Lots of thing are "true" in JS, in fact, only things that are "false" are the false Boolean, the numbers 0 and NaN, the empty string, null and undefined.

e.g., var e = e.chileNodes || e; e = e || window.e; opt.time = opt.time || 400;

Sunday, November 11, 2007






Thursday, November 8, 2007

CSS Hack

* html div.tableContainer { /* IE only hack */
   border:1px solid #ccc;
   height: 285px;
   overflow-y: auto;

* html div.tableContainer table thead tr td,
* html div.tableContainer table thead tr th{
/* IE Only hacks */

html>body tbody.scrollContent {
   height: 262px;
   overflow-y: auto;

tbody.scrollContent td, tbody.scrollContent tr td {
   background: #FFF;
  padding: 2px;

tbody.scrollContent tr.alternateRow td {
  background: #e3edfa;
  padding: 2px;

<div class="tableContainer">
<table dojoType="SortableTable" widgetId="testTable" headClass="fixedHeader" tbodyClass="scrollContent" ...>

It is excerpted from dojo sortableTable test file, since IE doesn't support overflow(scrollbar) on tbody, it plays a trick by wrapping a scrollable div around table, and relatively position the thead to simulate the effect.

* html only visible to IE, and child selector html>body not visible to IE. Also note that the descendant selector is applied above.

Wednesday, November 7, 2007


- MySQL has ENUM type, it is used to enforce the data integrity and de-normalize table. Those joins may add unnecessary complexity to your database.

create table reservations (
   reservation_id int unsigned auto increment primary key,
   seat_pref_description enum('Window', 'Aisle')

Should a user place a invalid value to this column, MySQL will block the incorrect entry and place NULL instead.

- CHAR and VARCHAR have maximum length 255 limitation, can followed by keyword BINARY, mean string comparison case sensitive. Longer string is stored as TEXT type or BLOB( for binary data).

- Two (or more) storage engines: INNODB, MYISAM. The default engine can be specified with parameter default-storage-engine in my.ini.
INNODB: support transaction safe, row-level locking and foreign key
MYISAM: faster, and support full-text searching

create table example(field1 int, field2 varchar(30)) engine=MYISAM;

Thursday, September 20, 2007

Map representation in Spring & Javascript

<bean id="action2Dashboard" class="java.util.HashMap" singleton="true">
       <entry key="/admin/license/installLicense.action">
       <entry key="/admin/agent/listAgents.action">

self.dashboards = {
  "/admin/license/installLicense.action" : "system:administration_setupsupport.3",
  "/admin/agent/listAgents.action" : "system:administration_agents.5"

var dashboard = dashboards["/admin/licnese/installLicense.action"];

Thursday, September 13, 2007


NotificationService {
publish(topic, payload);
subscribe(topic, listener);

Internally, each topic maintain a set of listener; Alternatively, topic is not used, uses more specific method such as addMouseClickListener(...) to specify it implicitly.

Callee subscribe/unsubscribe the listeners to interesting topic; When something happend (e.g, UI interaction, object added, removed or modified), event is fired, (The event carries all the context information such as source object, action, context etc that listener need to know). publish() is invoked to notify listener.

Listener {

Publish/Subscribe also known as Observer pattern. The Observer pattern defines an one-to-many dependency between a subject object and any number of observer objects so that when the subject object changes state, all its observer objects are notified and updated automatically.

In the diagram, we can see it doesn't pass an event/data/msg object though, one static reference to subject object is kept in concreteObserver to get the latest state information.

Tuesday, September 11, 2007

Javascript keyword "this"

function doSomething() { = '#cc0000';

In JavaScript this always refers to the “owner” of the function we're executing, or rather, to the object that a function is a method of. hen we define our faithful function doSomething() in a page, its owner is the page, or rather, the window object (or global object) of JavaScript.

An onclick property, though, is owned by the HTML element it belongs to.

element.onclick = doSomething;

The function is copied in its entirety to the onclick property (which now becomes a method). So if the event handler is executed this refers to the HTML element and its color is changed.

However, if you use inline event registration

<element onclick="doSomething()">

you do not copy the function! Instead, you refer to it, the this keyword once again refers to the global window

Thursday, September 6, 2007

Javacript location reload

- To refresh current iframe


- To refresh current page


one good example, after ajax session timeout, invoking this will be taken to logon page.

Wednesday, September 5, 2007

Struts 2 Architecture

At its core, Struts 2 is a Command pattern implementation. The framework encapsulate the execution of the action. Because you 're calling actions through a framework, you can configure the framework to add services around the call.

The ActionInvocation object provides access to the runtime environment such as action, interceptor, context (request parameters, session parameters, user locale, etc), result. It represents the current state of the execution of the action. e.g., it maintain the state and know which interceptor has been invoked.

ActionProxy serves as client code's handler.
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, context)

Tuesday, September 4, 2007

Javascript Object Literal

- Use object literals as flexible function parameters

Object literals are objects defined using braces ({}) that contain a set of comma separated key value pairs much like a map in Java.

{key1: "stringValue", key2: 2, key3: ['blue','green','yellow']}

The example above shows an object literal which contains a string, number, and array of strings. As you may imagine object literals are very handy in that they can be used as generic for passing in arguments to a function. The function signature does not change if you choose to require more properties in a function. Consider using object literal as the parameters for methods.

function doSearch(serviceURL, srcElement, targetDiv) {
var params = {service: serviceURL, method: "get", type: "text/xml"};

function makeAJAXCall(params) {
var serviceURL = params.service;

Also note that with object literals you can pass anonymous functions as may be seen in the following example:

function doSearch() {
makeAJAXCall({serviceURL: "foo",
method: "get",
type: "text/xml",
callback: function(){alert('call done');}

function makeAJAXCall(params) {
var req = // getAJAX request;, params.method, true);
req.onreadystatechange = params.callback;

Object literals should not be confused with JSON which has similar syntax. JSON is a subset of object literal notation of javascript.

self.dashboards = {
"/admin/license/installLicense.action" : "system:administration_setupsupport.3",
"/admin/agent/listAgents.action" : "system:administration_agents.5"

*** when needed, say there is '/' or space character, we can use double quote to escape it.

Friday, August 24, 2007

Javascript dynamic nature

1. eval() function
eval(string) is a top level built in function which lets you define and execute code on the fly. A programmer my not known in advance what code should be executed. Depending on values of different variables a particular statement may need to be executed at run time.

var fieldList = new Array('field1','field2','field3','field4','field5');
var tempObj;
for (count=0;count<fieldList.length;count++) {
tempObj= eval("document.myForm." + fieldList[i]);
if (tempObj.value.length==0) {
alert(fieldList[i] + " requires a value"); }

Also, eval() is used to parse JSON string to javascript object representation too.

var value = eval("(" + jsonText + ")");

2. The javascript object is essentially just an associated array, with fields and methods keyed by name. Javascript doesn' have built in concept of inheritence, every javascript object is really an instance of the same base class, a class that is capable of binding memeber fields and functions to itself (as associated array) at runtime.

myObject.shoeSize ="12" <=> myObject['shoeSize']="12"
myObject.speakShoeSize = function() { ...} <==> function speaksth() {...}

** note that NOT speaksth()

javascript function is a type of built-in object. A descendant of Object and can do everything that a Javascript object can such as having properties or other function object attached to it.

We can achieve polymorphic (dynamic binding) easily, for instance
myObject['methodName'](x, y, z); where methodName is a runtime passed in string

Wednesday, August 22, 2007

WebWork 2 to Struts 2 Migration Note

1. web.xml
Update FilterDispatcher, ActionContextCleanupFilter and SitemeshFilter to use Struts

2. JAR libs
Remove webwork-2.2.5.jar, xwork-1.2.2.jar, xwork-tiger-1.2.2.jar
Use struts2-core-2.0.9.jar, xwork-2.0.4.jar, struts-sitemesh-plugin-2.0.9.jar and struts-spring-plugin-2.0.9.jar. Note that the extensions are separated into different plugin jars.

3. xml Config files
Rename xwork.xml to struts.xml, xwork-agent.xml to struts-agent.xml, xwork-audit.xml to struts-audit.xml, etc.
in the config files, change some occurences of 'xwork' to 'struts' and change DOCTYPE to use struts-2.0.dtd

4. property files
Rename to and change all occurences of 'webwork' to 'struts' in the property file
multipart parser set to jakarta which replaces com.opensymphony.webwork.dispatcher.multipart.CosMultiPartRequest
change ui templateDir to admin/themes/struts

Note that the conversion property file is still called

***NOTE that in, set struts.i18n.encoding=UTF-8, otherwise it will cause problem in working with dojo contentPane. By default, struts will use charset CP1252 encoding for windows server and UTF-8 encoding charset for Linux.

5. validatiors.xml
Change all com.opensymphony.xwork to com.opensymphony.xwork2, and the DOCTYPE must be defined to avoid SAXParser exception. It is allowed for webwork though.

6. Action classes
Imported package name: com.opensymphony.xwork change to com.opensymphony.xwork2 and com.opensymphony.webwork change to org.apache.struts2
Class Configuration is changed to DefaultSettings. It seems that Struts 2 provides better support in custom property config setting
AroundInterceptor is removed from xwork2, use MethodFilterInterceptor instead.

7. On all FreeMarker ftl pages
Rename webwork tag prefix @ww. to @s.

8. FreeMarkerManager, ActionSupport API
Change OgnlValueStack type to ValueStack in parameter list.

9. FileUpload
The random generated file names such as xxx.tmp is used and the files are stored in a upload folder on the server when we upload the files. So the features such as install cartridge, script agent builder are broken because they will get the random generated file name instead by calling File.getName() or File.getAbsolutePath(). To obtain the original file name, MultiPartRequestWrapper provides a method getFileNames() method, and for FileUploadInterceptor, the file name is injected.

Tuesday, August 14, 2007

JBoss / JMX

- JBoss architectural layers: JMX microkernel, service layer, aspect layer, application layer

- JBoss server and container completely uses component-based plug-ins. The modularization is supported by JMX.

- JMX is the best tool for integration of software. It provides a common spine (or common bus) that allows the user to integrate modules, containers, and plug-ins. Components are declared as MBean services that are then loaded into JBoss and subsequently be administered using JMX.

- JMX is an isolation and mediation layer between manageable resource and management systems. Resources use JMX to make themselves manageable by a management system to ensure high availability and utilization.

- JMX is an internal instrumentation API.

Monday, August 13, 2007

Java Security

- Codebase + signer = Code Source
In java policy file, the code source associate various permissions to create protected domain.

grant signBy "sdo", codeBase "" {
permission "/tmp", "read";
permission java.lang.RuntimePermission "queuePrintJob";

The default policy for all JVM in $JREHOME/lib/security/java.policy; And by default the sandbox for java applications is initially disabled.

- KeyStore (code signing)
The certificates are held in a location (usually a file) called the keystore. For developer, keystore is consulted to find the certificate used to sign your code; For end user or system admin, the keystore is consulted when you run signed code to see who actually signed the code.

- Certificates
Two types of keys: asymmetirc (private/public key pair) and symmetric (secret key). Certificates are used to authenticate public keys; when public keys are transmitted electronically, they are often embedded within certificates. It is issued by well- know entity (Certificate authority, or CA). The certificate contains a digital signature of the CA. So we have a bootstrapping problem here - how do we obtain the public key of the certificate authority to authenticate the certificate itself?

Friday, August 10, 2007

MySQL Admin

By default, the user cannot connect to mysql db remotely. To enable remote access to a mysql db, suppose the user connect from host IP

c:>mysql> mysql --user=root --password=mypassword
mysql> grant select,insert,update, delete on db_name.* to user_name@ IDENTIFIED BY "user_password";

mysql>show schemas;
mysql>show tables;
mysql>show columns from tbl;

mysql>select * from tbl into outfile 'C:/myexport.txt';

mysql>select * from tbl\G; // the option \G format the output

Wednesday, August 8, 2007

WebWork value stack & Interceptor

The value stack is central to the dynamic context-driven nature of webwork. A stack of objects against which expressions can be evaluated to find property values dynamically, by searching for the first object (from the top of the stack down) that has a property of that name. WebWork builds up the value stack during execution by pushing action onto stack (so action property is available to expressions in view pages). Note that not property itself but the contained object in the stack.

It provides a convenient way to expose common values such as security permission to the front-end. Creating a custom interceptor SecurityGateInterceptor, which push a secInfo object into the value stack, then all the properties available through getxxx() in SecInfo object will be exposed to all the view pages.

Relate methods are: context.getValueStack.push(obj); context.getValueStack().findValue("propertyName");

Interceptors are conceptually the same as servlet filters and JDK's Proxy class. They provides a way to supply pre-processing and post-processing around the action. With it, we can do dependency injection using a combination of set injection and interface injection. With the assistance of interfaces such as PrincipalAware, SessionAware, etc.

Monday, July 30, 2007

Javascript validation using keyCode check

The user is not able to enter certain characters using this approach.

function isDigit(e) {
if (!e) var e = window.event;

var code;
if(e.keyCode) {
code = e.keyCode;
} else if(e.which) {
code = e.which;
// 8-backspace, 9-tab, 13-enter, 46-delete
if (code == 8 || code == 9 || code == 13 || code == 46) {
return true;
var character = String.fromCharCode (code);
return '0' <= character && character <= '9';

<input type="text" name="versionMajor" onkeypress="return isDigit(event);" ../>

onkeydown: Returns a unique integer keyCode representing the the physical key that is pressed. e.g., "1" key on top of keyboard produce "49" whereas in numeric keypad produces "97" since they are different keys. For details.
onkeypress: Returns an integer keyCode representing the Unicode value of the character associated with the key. "1" key produces "49"; Also, for those shift, alt, ctrl combination keys, the combined result is produced such as SHIFT+3 = '#'

The onkeypress (or onkeydown) event hander to respond to a keystroke and to capture the keyCode. If the function returns true and the keystroke produces the character in the textbox. Otherwise, the function returns false and the character is not produced.

The function String.fromCharCode(unicode) is used to convert unicode to ASCII character.

BTW, the comparison of onmousedown and onclick:
onmousedown will trigger when either the left or right (or middle) is pressed. Similarly, onMouseUp will trigger when any button is released. onmousedown will trigger even when the mouse is clicked on the object then moved off of it, while onMouseUp will trigger if you click and hold the button elsewhere, then release it above the object.

onclick will only trigger when the left mouse button is pressed and released on the same object. In case you care about order, if the same object has all 3 events set, it's onmousedown, onMouseUp, then onclick. Each even should only trigger once though.

Wednesday, July 11, 2007


- A distributed system logically divide into two pieces: actual business/functional code and infrastructure/pluming code (middleware). Definition of middleware: "The wide range of software layered between applications and an operating system that provide specialized services and interoperability between distributed applications" There are Remote Procedure Call (RPC) based middelware and Message-oriented Middleware (MOM).

- JMS is a specification that defines a set of interfaces and associated semantics, which allow applications written in Java to access the services of any JMS compliant Message MOM product.

- JMS supports two messaging styles: point-to-point and publish-and-subscribe. Point-to-point system typically either a one-to-one to many-(senders)-to-one (receivers); publish/subscribe is a many-to-many system. (Queue vs. Topic)

- Administrable objects - destination and connection factory, not standardized by JMS. JMS defines "marker" interface, but JMS providers create and customize the objects, used by client to gain access to messaging product.

- ConnectionFactory => Connection => Session (QueueSession, TopicSession) => MessageConsumer (QueueReceiver, TopicSubscriber), MessageProducer (QueueSender, TopicPublisher)

Friday, June 29, 2007

Confucius say

What the superior man seeks is in himself; What the small man seeks is in others
- Confucius

Tuesday, June 26, 2007

EJB 3.0

- Server-side component framework: Dividing functionality into independent and self-contained components that can interoperate with each other to assemble an application was deemed the best solution for building applications.

- Component vs. Object: Object-oriented languages/technoglogy by itself wasnot fully equipped to garner optimum levels of reuse.Components differ from objects in a substantial manner - they live in an independent existence. A component is a self-contained, reusable entity, it must be packaged with all the requisite artifacts.

- Simplicity in EJB 3.0 has been achieve:
a) No home and object interfaces are required. Pre EJB 3.0, need provide home interface (local/remote), EJB object interface (local/remote) and a bean class.
Home interface server as a factory for creating references to EJB object. SL session bean and message driven bean doesn't have state, can be create equally. SF session bean has a special create method in home interface to pass state, in 3.0, a special method can be defined in bean class to handle it, called by container before 1st business method invoked.
Object interface provide client view for an EJB, container provides implementation for it, i.e., the EJB Object. It delegate the business method call to bean class, whereas calling container middleware service before and after the method invocation. The problem is that bean class doesn't directly implement the object interfaces and developers should be extra careful in providing the implicit implementation to match the method signatures. (one solution is define a super business interface)
** Local interface passes parameter using pass-by-reference semantics, remote interface pass-by-reference
** For every request from a unique client, does the container create a separate instance of the generated EJBHome and EJBObject classes? - The EJB container maintains an instance pool. The container uses these instances for the EJB Home reference irrespective of the client request (bean in instace pool servers only for home methods such as create and findBy). while refering the EJB Object classes the container creates a separate instance for each client request.
In EJB 3.0, only a POJI business interface and a POJO directly implementation bean class needed, container will internally generate wrapper class, i.e., how the container inject middleware service before and after business method invocation is a container-specific detail and need not concern the EJB developer. Also, 2.+'s RemoteException in remote interface is removed, replaced with a EJBException which is unchecked RuntimeException and not need to be listed in the method signature.

b) No component interface is required
javax.ejb.SessionBean and javax.ejb.MessageDrivenBean to notify bean instance of variosu life cycle events such as ejbCreate(), ejbDestroy() are not enforced. You can use @interpectors annotation to add a callback class to handle them if needed.

c) Use of Java metadata annotation
It makes deployment descriptors redundant. If there is deployer, should still use DD to separate the responsibility.

d) Simplification of APIs for accessing bean's environment: don't have to rely on JNDI, dependency injection and EJBContext.lookup() available

- Session Bean
Components that contains logic for business processes, relatively short-lived object, lifetime equivalent of a session or request.

Three subTypes: SLSB, SFSB, and session bean as web service (2.1+)

SLSB: no conversational state. In EJB 2.+, bean instances are pooled (a few beans can server many clients) and reused, they are decoupled from EJB objects. For SL, the container dynamically reassign beans to client requests at the per-method level. The create(), remove() in home interface are for creating and destroying EJB objects, not bean instances which are managed by container in bean pool. (So from SL client code, operate on same EJB object may actually delegate to different bean; SF is different)

SFSB: hold conversational state spanning multiple method call. Same bean has to be used for same EJB object and client requests, meanwhile, need bean pooling, so activation/passivation are introduced.
** One of the most common programming mistakes in J2EE is to forget to explicitly destroy or remove session bean (SF) once they have been used, a big performance impact. Basically, what happens is that the SF bean will be passivated,a rather silly way of removing an bean from the container (or removed when session timeout). As you probably know, passivation is a very expensive operation, as it first serializes the bean, and then writes it to disk. In EJB 2.+, call remove() method in EJBObject interface; in EJB 3.0, add a method annotated with @remove in business interface, call it will tell container that the client doesn't need it anymore and can be destroyed.
** SFSB vs. Http Session object
SFSB has limited used in development should rarely be seen. Shopping cart as referenced example cannot satisfy the persistent failure. The primary intent of SFSBs is used in non-web system to track session-oriented state. In web system, should use HttpSession objects to store session-oriented state on behalf of a client. Applications that manage an HttpSession object and an SFSB for a single client wind up duplicating effort that does not need to be duplicated. only used in two cases: 1. system is expected to have a large number of concurrent clients, since SFSB has passivation/activation, implemented as HttpSession only hold one reference to a SFSB; 2. session-oriented objects need to receive notifications on the lifecycle of the transactions they participate in. SFSBs can implement the SessionSynchronization interface. (Arguable: different responsibility for web-tier against business tier, lower reusebility)

Session Bean as Web Service
Web service has not single implementation framework, a contract with WS only involve its interfaces (port), which defined in WSDL, and can be implemented in any language.
Web services = WSDL + SOAP + UDDI;

- Entities
Java Persistence Specification: provide standard ORM; not tie to J2EE container, can be used in J2SE env; define a service provider interface SPI

Entities vs. Session Bean: have persistent identity (PK), persistent state, not remotely accessible, different life cycle with application

- Entity bean (CMP & BMP)
passviation/activation Entity bean vs. SFSB:
SFSB passivation/activation uses object serialization to persist states, ejbActivated() and ejbPassivate() are called to restore/release the resources such as socket connection;
Entity bean passivation/activation is to transition bean into or out of instance pool. In activation, container associate bean with a specific EJB object and a specific primary key. call ejbActivate(), ejbLoad() in order, ejbActivate() is to acquire resource, and ejbLoad()read db data to bean state; In passivation, deassociate bean from EJBObject and PK, call ejbStore() and ejbPassivate() in order.
** container cannot passivate a bean when it is in a transaction

More methods: findXXX() in home interface and corresponding ejbFindXX() in bean class for BMP, findByPrimaryKey() is mandatory, others are custom finder; getPrimaryKey() in EntityContext to be used in bean ejbLoad() and ejbRemove() in bean, EJBObject has a getPrimaryKey() method to be used in client code. We have remove() in both EJBHome and EJBObject interface, the former one is only for entity bean that we can remove it without instantiated first.

EJB 2.0 CMP vs EJB 1.1 CMP
In EJB 1.1 CMP there was no defined standard for defining relationships, finder methods or local beans; EJB 2.0 CMP more clearly defines the roles of entity beans, includes standard query definition (EJB QL), local interfaces and abstract classes. The abstract class model require to declare abstract accessor methods, which is required by the EJB 2.0 specification to use Container Managed Relationships (CMR), the container will generate the code to implement the relationship into the accessor methods of the EJB wrapper object.

CMR (container managed relationship 1-1, 1-N, N-M)
It is to establish a relationship between two CMP entity beans
Multiplicity defined in deployment descriptor with
Navigability (uni-direction and bi-direction) is implied by the definition of abstract getter and setter method in one or both of the entity beans. Also, a is added to deployment descriptor.
** CMR fields (as opposed to CMP fields) cannot be initialized in ejbCreate() method because PK is not available atm, They can, however, be set in ejbPostCreate()

- Message Driven Bean
Message-driven beans process multiple JMS messages asynchronously, rather than processing a serialized sequence of method calls. Message-driven beans have no home or remote interface, and therefore cannot be directly accessed by internal or external clients. Clients interact with message-driven beans only indirectly, by sending a message to a JMS Queue or Topic. Only the container directly interacts with a message-driven bean by creating bean instances and passing JMS messages to those instances as necessary. The Container maintains the entire lifecycle of a message-driven bean

MOM - message-oriented middleware vs. RMI
.aynchrony, non-blocking request processing
.decoupling, message sender decouple from consumer
.reliability, guarantee delivery
.support for multiple senders and receiver, broadcast

Friday, June 22, 2007


- take control of compilation, packaging, testing, and deployment stages of the build process in a way that is portable, scalable, and often reusable.

- three levels: project (each build file), target (dependencies), task (extensible, e.g, javac, mkdir)

<-- target is to resolve dependencies, conditionally execution. e.g., -->
<target name="m.clean">
  <property name="cleaned" value="true"/>
<target name="m.compile">
  <javac destdir="..." ></javac>
  <antcall target="m.binding" />

<target name="m.binding" if="cleaned">
   <bind load="true"> ...</bind>

- DataTypes: Path, Fileset, Patternset, Filterset, ZipFilest, etc.

- Ant properties ${}
i. built in properties such as ant.file (the absolute path of the build file, can use ant.file.fgl-common-build where fgl-common-build is the project name of the build file which is included in the main build file)

ii. JVM system properties, e.g.,, java.home

iii. Set with 'property' task

<property name="build.debug" value="on"/> -- name/value pair
<property file="" /> -- load from properties file
<property name="src.dir" location="somedir" /> - refer to relative path

iv. load environment varialbe

<property environment="env" />

v. set property conditionally

<!-- if debug, set debugLevel to all; else lines only -->
<condition property="debugLevel" value="lines,vars,source" else="lines,source">
  <istrue value="${debug}"/>

all env vairables are loaded with prefix env such as env.CATALINA_HOME
Properties are immutable. A property define in build file doesn't override the one from property file. However, there are ways to break the immutability of property using <ant/>, <antcall>, <available> and -D command line operation. Among them, -D has the highest priority.

Tuesday, June 19, 2007

MBean definition

Java Management Extensions (JMX) is a Java technology that supplies tools for managing and monitoring applications, system objects, devices (e.g. printers) and service oriented networks. Those resources are represented by objects called MBeans (for Managed Bean).

MBean is a type of JavaBean, created with dependency injection. The MBean represents a resource running in the Java Virtual Machine such as an application, a J2EE technical service (transactional monitor, JDBC driver, ...), ... They can be used for getting and setting applications configuration (pull), for collecting statistics (pull) (e.g. performance, resources usage, problems, ...) and notifying events (push) (e.g. faults, state changes, ...)

Friday, June 15, 2007


- allow a smooth transition from machine-centric to human-centric coding, on bytecode level, it is java. It builds on the basic idea of Java platform with a different interpretation of code appearance.
- Groovy is a dynamic language: generating classes transparently at runtime and able to seemingly modify classes at runtime. Groovy doesn't generate bytecode that reflects method call directly, but does something like
getMetaClass().invokeMethod(this, "foo", EMPTY_PARAMA_ARRAY); That way, method calls are redirected through the object's MetaClass. The MetaClass can now do tricks with method invocations such as intercepting, redirecting, adding/removing methods at runtime, and so on.
- run Groovy classes inside JVM in two ways: precompiled mode (with groovyc compiler to generate *.class file, loaded with normal Java classloader) and direct mode (no *.class files are generated, but rather class objects, loaded with groovy classloader)
- treat everything as an object and all operators as method calls, hence improves the level of object orientation. autoboxing for primitive types, 1+1 is interpreted as
- closure needed in: iterator for collections, and using resource in safe manner.

Thursday, June 14, 2007


Spring is a lightweight inversion of control and aspect-oriented container framework.
- lightweight: size/overhead, and nonintrusive, app doesn't have dependencies on Spring specific classes
- inversion of control: object are passively given the dependencies instead of creating or looking for dependencies. beans loosely couple through interface.
- aspect-oriented: enable cohesive development by separating application business logic from system services (commonly referred to as cross-cutting concerns because they tend to cut across multiple components) such as auditing and transaction mgr.
- container: contains and managers the life cycle and config of app objects, creating associations between collaborating components (wiring beans)
- framework: config and compose complex app from simple component.

Common implementations of ApplicationContext:
- ClassPathXmlApplicationContext: load context definiton as xml from classpath
- FileSystemXmlApplicatonContext: load from file system
- XmlWebApplicationContext: load from xml contained within a web application

Tuesday, June 12, 2007

Java 5 Enum

Java 5 enum is a special type of class - subclass of java.lang.Enum. Java compiler transparently translate it. Each symbolic constant of enum is an instance of the class. The constructor must be private to avoid programmatically instantiation. It can also have private attribute and methods.

enum TrafficLight {RED, GREEN, BLUE; }
public class TrafficLight {
private final String color;
private TrafficLight(String color) { this.color = color; }

public static final TrafficLight GREEN = new TrafficLight(“GREEN”);

So,on the caller side:

TrafficLight tl = TrafficLight.RED;


enum TrafficLight {RED("stop"), GREEN("go"), BLUE("warn"); }

where the string is passed in as constructor parameter.

Also, since it is normally inside a file with other class, it cannot be declared as public in that case.

-Some common methods

public enum Shape { RECTANGLE, CIRCLE, LINE }
System.out.println(drawing); // Prints RECTANGLE, toString() will return name
System.out.println(drawing.ordinal()); // Prints 0 for RECTANGLE.
Shape drawing = Shape.valueOf("RECTANGLE"); // convert a string to Enum object

Reference Link

Thursday, May 31, 2007

Bitwise AND operator

To alternate the table background color:

rows [i].className = ((rows [i].index & 1) == 1) ? "alt" : "";

Friday, May 18, 2007

Javascript RegExp

- Example 1

var regex = /<title[^>]*>([\s\S]*?)<\/title>/i;
while(match = regex.exec(httpResp)){
if ('Login Page' == match[1]) {

"?" is important in the above regexp. By default it is greedy mode, with ?, it means the group will stop at the first encountered '<';

exec return ["<title>Login Page</title>", "Login Page"] since group () is used in regex which is useful to extract interested content.

-Example 2

html += ' <a href="javascript:showManyToMany (groupsForUser, \'' +\'/g, "\\'").replace(/\"/g, "&.quot;") + '\');">Edit</a>';

In this case, single quote has to be escaped by javascript \\'; however Double quotes has to be escaped by html as &.quot; rather than \\", otherwise, "unterminated string literal" error occur. It looks like browser can understand \', but it doesn't
understand \".

- Example 3
cartName.replace(" ", "_"); will only replace first occurrence of space character. It should be cartName.replace(/\s/g, "_"). 1st parameter is regExp, rather than string, and /g means global search for all occurrences of a pattern. Note that the regular expression doesn't need to be quoted.

Thursday, May 17, 2007

Session timeout handing in AJAX call

DWR has a global handler textHtmlHandler for this purpose. When a DWR request receives a response that isn't Javascript. It indicates a server session has timed out, and we can redirect user to logon screen in this handler. So any DWR call doesn't need to worry about session timeout.

dwr.engine.setTextHtmlHandler(function() {

Unfortunately, Dojo doesn't have such mechanism, so for each or FormBind, we have to deal with it individually as shown below.

load: function(type, data, evt) {
sessionTimeoutCheck(data, evt);

I have modified dojo source code (doLoad method in BrowserIO.js) to support a global/transparent session timeout for dojo. I don't think Dojo itself will support this feature in the future. This is because dojo's response could be 'text/html' mimeType, which is typically used in setURL of contentPane. And the server normally returns the logon page when session timeout occurs. So dojo cannot know whether it is a requested page or timeout page. DWR is simplier since it doesn't support text/html and we can treat a html response as timeout.

** More on DWR's error handling - It has global/batch/call three levels of fine-grained support.

global error handler:


call level error handlers:

Remote.method(params, {
callback:function(data) { ... },
errorHandler:function(errorString, exception) { ... }

or, in batch level:

Remote.method(params, function(data) { ... });
// Other remote calls
errorHandler:function(errorString, exception) { ... }

Javascript undefined vs. null

In JavaScript, undefined means a variable has been declared but has not yet been assigned a value, such as:

var TestVar;
alert(TestVar); //shows undefined
alert(typeof TestVar); //shows undefined

null is an assignment value. It can be assigned to a variable as a representation of no value:

var TestVar = null;
alert(TestVar); //shows null
alert(typeof TestVar); //shows object

From the preceding examples, it is clear that undefined and null are two distinct types: undefined is a type itself (undefined) while null is an object.

Unassigned variables are initialized by JavaScript with a default value of undefined.

JavaScript never sets a value to null. That must be done programmatically. As such, null can be a useful debugging tool. If a variable is null, it was set in the program, not by JavaScript.

null values are evaluated as follows when used in these contexts:

Boolean: false
Numeric: 0
String: “null”

undefined values are evaluated as follows when used in these contexts:

Boolean: false
Numeric: NaN
String: “undefined”


null is an object. It's type is null. undefined is not an object, it's type is undefined.

Use if (SomeObject) to check value existence is not robust. It will also catch the cases of SomeObject having numeric value of 0, or string value of empty string. Because their boolean coercion is also false. See, in JavaScript, null and undefined are actually equal according to the == and != operator! (ECMA-262 standard, section 11.9.3 tells us so.) So we have null == undefined. In vast majority of cases, you don't care about the difference at all, so using someExpr != null is good enough.

If we really need to differentiate null and undefined, use

typeof(SomeObject) == 'undefined'

Session timeout handing in AJAX call

DWR has a textHtmlHandler that allows you to manage what happens when a DWR request receives a response that isn't Javascript. This generally means that a server session has timed out, so it is usual in a textHtmlHandler to redirect to a login screen.

dwr.engine.setTextHtmlHandler(function() {
//automatically take user to logon page due to timeout

You set a global error handler like this:


You can also specify call or batch level error or warning handlers. For example, in the call meta-data:

Remote.method(params, {
callback:function(data) { ... },
errorHandler:function(errorString, exception) { ... }

or, in batch meta-data:


Remote.method(params, function(data) { ... });
// Other remote calls

errorHandler:function(errorString, exception) { ... }

Unfortunately, Dojo doesn't provide such global error handler and has to be handled individually on each onLoad().

load: function(type, data, evt) {
sessionTimeoutCheck(data, evt);

Tuesday, May 15, 2007

JSF study notes

- a Java component-oriented web framework (RAD)
- allows developers to think in terms of components, events, backing beans and their interactions, instead of requests, responses, and markup.
- higher level abstraction, Servlets and JSP were developed to make it easier to build applications on top of HTTP protocol. JSF was introduced so that developers can forget that they're using the protocol at all.

def: a software component is a unit of composition with contractually specified interfaces and explicit context dependencies only (container). A software component can be deployed independently and is subject to composition by third parties.

1. value change event
2. data model event - data row selected
3. action event - command from button or link
4. phase event - request processing life cycle event

Phases: (translate http request to events and update server side components value)
1. restore view
- find/create components tree, including event listeners, validators,converters associates with components; if initial request, skip to phase 6

2. apply request values
- update value of components to values in request, converter may get involved
-'immediate' property may trigger action event, handy for 'Cancel' button

3. process validation
- component validate itself
- value change events fired

4. update model values
- update value of backing beans or model objects associated with components

5. invoke application
- action events fired

6. render response
- render response, and save view in user session (?) to be restored later

Question: why may date model events be fired in phase2 - phase 4?

- 且就洞庭赊月色, 将船买酒白云边。

Webwork ScopeInterceptor

ScopeInterceptor is used to pull attributes out of session/applicaton and set action properties, i.e., dependency injection.

<action name="handleGeneralError" class="...">
<interceptor-ref name="scope">
<param name="key">FoglightError_</param>
<param name="session">exception,statusCode,originalRequestUri</param>
<param name="type">end</param>
<result .../>

key - key prefix
session - a list of action properties to be bound to session scope
type - 'end' means remove from session after action run; 'start' means it's a start action of the wizard-like action sequence and all session scoped properties are reset to their defaults; any other value or no value means that it's in-the-middle action that is set with session properties before it's executed, and it's properties are put back to session after execution.

Monday, May 14, 2007

HTTP Status Code & Error Handling

1xx Informational
2xx Successful (200 OK)
3xx Redirection (302 Found)
4xx Client Error (403 Forbidden 404 Not Found)
5xx Internal Server Error

We can define how a web application handles errors using the error-page element in the WEB-INF/web.xml file. The error-page element defines exceptions by exception type or by error code, as the following sections describe. The order of these elements in the web.xml file determines the error handler.


<exception-type> </exception-type>
<location>/error-pages/404.jsp </location>

The HttpServletRequest and HttpServletResponse objects provide access to error information

Object status_code = req.getAttribute("javax.servlet.error.status_code");
Object message = req.getAttribute("javax.servlet.error.message");
Object error_type = req.getAttribute("javax.servlet.error.exception_type");
Throwable throwable = (Throwable) req.getAttribute("javax.servlet.error.exception");
Object request_uri = req.getAttribute("javax.servlet.error.request_uri");

With such error information, we can delegate the error handling to one Action class which provides fine grained control, e.g, printStackTrace on screen on dev mode.

Tuesday, May 8, 2007


A British man who went on a wild spending spree after doctors said he only had a short time to live, wants compensation because the diagnosis was wrong and he is now healthy - but broke. John Brandrick, 62, was diagnosed with pancreatic cancer two years ago and told that he would probably die within a year. He quit his job, sold or gave away nearly all his possessions, stopped paying his mortgage and spent his savings dining out and going on holiday. It emerged a year later that Brandrick's suspected "tumour" was actually an inflamed pancreas.

- spree: a time of free and wild fun, spending, drinking, etc.
- inflamed: (of a part of body) read and swollen because hurt or diseased

Thursday, May 3, 2007

WebWork Result Types

Besides the default result type "dispatcher", there are some common result types:
1. redirect
Important for those create actions, reload confirmation page may result in re-create sth if URL stick to the previous create action. Parameter can be passed in by value in stack. e.g,
.. type="redirect">listBundles.action?archiveFilename=${archiveFilename}

2. chain
Action chaining is different from dispatching to another action in that:
- In same ActionInvocation
- Copy common properties from most recent action
In both cases, every action executed is pushed onto stack since they run in same thread/request.

3. StreamResult
For download file. BTW, use setTimeout() on init() to both refresh a page and download a file sounds a better solution than using refresh metatag which causes 'back' button problem.

We may even create customized ResultType such as JSON for AJAX call.

Wednesday, May 2, 2007

Some FreeMarker operators

- Number
value?c converts a number to string for ``computer audience'' as opposed to human audience.e.g., number 3600 will be parsed as 3600 instead of 3,600

- Handle missing values
exp1!exp2 deprecates exp1?default(exp2)
exp1! deprecates exp1?if_exists (replace with empty string "", empty set etc if not exists)
exp1?? deprecates exp1?exists


This examines if the product hash contains a subvariable called color or not, and returns "red" if not. The important point is that product hash itself must exist, otherwise the template processing will die with error.

This examines if product.color exists or not. That is, if product does not exist, or product exists but it does not contain color, the result will be "red", and no error will occur.

Monday, April 30, 2007

DWR's loading message may result in event "lost" in Firefox

When those 'edit' links in manage users/groups/roles are clicked, two events are actually triggered, 1st one is onmousedown event to select table rows (a DWR server call), 2nd one is onclick event to display the dialog (local). Then when we have DWRUtil.useLoadingMessage in page's init() to display loading message. In FireFox, the 2nd onclick event is not captured if the user release the mouse during the message display period. (this is very obvious in QA server, it take a little bit longer to return the DWR call; in our local dev server, it run so fast so almost never happen.) This is because when displaying such loading message, Firebox blocks UI. The release of the mouse is ignored during that block time, thus onclick event isn't triggered. IE doesn't have this problem though.

Note: onmousedown does not garantee that an onclick event will occur on the same target. For example, if you mouse over a link and then press and hold the click button, but then you move off of that link and release the button, the link will not be clicked.

Wednesday, April 25, 2007

Avoid XMLHttpRequest caching

var url = "../index.html?randomKey=" + Math.random() * Date.parse(new Date());

The task is to poll server periodly for availability with a scheduled XMLHttpRequest (using JS's setInterval("heartbeat()", 15*1000); However, in IE the response is cached even though those meta tags such as 'cache-control', 'expired' have been set to no-cache. And I have tried to set these in request header and not helpful either. The ajax calls are still cached and always return status 200 even though server is down and 404 should be returned. The IE caching is based on url, therefore, append a random key will resolve this issue.

Saturday, April 21, 2007


Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries, which can in turn affect the semantics of the running program. Annotations can be read from source files, class files, or reflectively at run time.

They allow you to define metadata in a typesafe way and apply it to a class, method, constructor, field, or parameter.

So what can use use annotations for? Some people envision using annotations for code generation and a replacement for XDoclet. Others, like the J2EE and EJB 3.0 expert groups, see it as a replacement for deployment descriptors. Furthermore, annotations can be used with AOP.

Friday, April 20, 2007

DWR's Method Signature

JS client side

SecPasswordChecker.preLoginCheck (name, password, callback);

Java server side

public String preLoginCheck (String userName, String password, ServletContext servletContext) {..}

The method signatures of DWR method on Javascript side and server side are not equivalent. JS side declare callback whereas server side allows us to add HTTP servlet object (i.e. HttpServletRequest, HttpServletResponse, HttpSession, ServletContext or ServletConfig) declared on your method. DWR will not include it on the generated stub and upon a call of the method it will fill it in automatically.

Thursday, April 19, 2007

DWR's async trick

function submitUserAction() {
sdgControlSelectedRows (userGrid, method);
sdgRequestRows (userGrid);

First line delete some rows, second line requests lasted rows after udpate. Both using DWR invocation.It is surprising to see that the rows on the table not changed althought the selected rows are deleted on server side unless we intentionly refresh/reload the page. It is due to the asynchronous nature of AJAX call. 2nd statement is executed before the 1st one finished -- i.e, before the row deletion. Both method invocations are queued in ajax engine, and invoke server side in order, however, server's response may come back in any order, thus, 1st request involved DB operation and take longer time and 2nd request always come back earlier and its callback invoked first. It results in the table not being refresh.

The solution is to add them to a batch and treated as an atomic request, similar to a transaction.

function sdgControlSelectedRows (gridObject, method) {
if (! dwr.engine._batch) {
DWREngine.beginBatch ();
gridObject.gridServer.controlSelectedRows (method, {
callback: function (response) {
if (response != 'success') {
dojo.event.topic.publish("actionMessageTopic", {message: response, type: "ERROR", delay: 4000});
errorHandler: function (message) {}
sdgGetRows (gridObject, "control");


Wednesday, April 18, 2007

Dojo's Formbind


var x = new{
formNode: "configureDirectoryForm",
mimetype: 'text/json',
load: function(type, data, e) {
alert("type=" + type + "Data=" + data);

x.onSubmit = function(form) {
// validate form or dispaly loading msg
return true; // need this, otherwise form won't get sent!

formNode: dojo.byId("agentCreationForm"),
method: 'post',
mimetype: 'text/json',
load: function(type, data, e) {
alert("type=" + type + "Data=" + data.state);
error: function(type, data, e) {
alert("An error occured!");

However, it has the limitation in form validation. It only allow client side JS validation. If we use webwork's validation framework, the response is html page with error msg rather than JSONObject string and cannot be interpreted.

Saturday, April 14, 2007

Javascript with FreeMarker & WebWork

window.parent.lastAction = "${request.requestURI}";

FreeMarker's interpolation ${expression} works inside Javascript too. Through it, WebWork's action context, value stack are accessible in Javascript and is useful in some cases. Note that the expression needs to be quoted.

Another example is:

<@ww.text id="LoadingText" name="common.message.flow.loading"/>

in script:

var messageDialog = dojo.widget.byId ('messageDialog');
messageDialog.domNode.innerHTML ="${LoadingText}"

In this way, the internalizationed text is passed to Javascript since the text is saved in the action context with the specified id attribute. [The objects in webwork are stored in five scopes: default (action context), session, application, request. Action context is a ThreadLocal storage available only during action execution.]

-- 山光悦鸟性, 潭影空人心。

Monday, April 9, 2007

Year 2002: Go East - N.B (3)


起床前又下了一阵小雨,然后天空顿时晴朗起来。北虹睡得很好,精神十足。想着昨日天色灰暗,灯塔的照片没拍好,于是又去Peggy's Cove转了一圈。海水湛蓝而宁静,昨日的景色荡然无存。虽然又拍了一张灯塔的照片,光线也很好,可是总觉少了些什么,心有些空落落的。原来大自然最美妙 的时刻并不是在阳光灿烂之时,而是在那幽明之间啊。



我们又重新回到新布朗斯维克省。沿着Bay of Fundy(Fundy 海湾)西行,可以看出新布朗斯维克省海边这儿要比山区那部分富庶得多。途经Fundy Shore Eco-tour,一条西行沿Fundy海湾的风景线,在Parsboro的Info Center要了些有关地图资料。景点Spencer's Island以一个1902年的灯塔而著名,到达时见一个胖妞坐在灯塔门口台阶处晒太阳。灯塔内有展览,而她是这儿的看守人。原来周围的海滩是过去一个重 要的造船中心所在地,那时所有的船只都露天在海滩上建造。无意中发现这儿有个故事,历史上最神秘船只之一的Mary Celeste就是在此建造并于1861年启航的。1867年这艘船在一次航行中全体船员、船长及妻子和两岁女儿都离奇失踪,而船被发现漂流于海上。所有 物品都保持原样,衣服挂在衣橱里,烟斗在桌上,货物八百桶美酒也纹丝未动,没有遭遇海盗袭击的迹象。后来,厄运一直伴随着这艘船,以至于船主发现他已很难 雇到船员了。最后此船沉没于海地附近的暗礁中。





早晨去逛退潮后的海滩。Fundy海湾有着世界著名的大海潮,最高处高达16米,此公园内平均9米。渔船们出海都得算好回来的时间,得趁着 涨潮回来。鱼儿们若退潮时跑不及,就会在沙滩上被晒成鱼干。我们就发现了一条有两个巴掌大小的怪鱼冤死在沙滩上,死不瞑目。虽然潮水落差大,但这儿涨潮并 不象新斯科舍省那样咆哮而来,而是静悄悄地漫过来,但却很快,若你在沙滩上站一小时,海水就会漫到你的脖子了。走在裸露的沙滩上,突然发现有许多很美的彩 色石头。于是动了个念头,捡回去摆个枯山水什么的。我们便开始捡起石头来,一发不可收拾,包括后来去的两个海滩,我们都是猫着腰,眯着眼,在沙滩上翻寻 着。



Fundy海湾位于新布朗斯维克省和新斯科舍省之间,呈喇叭形。大西洋的海水汹涌进入此海湾,在Hopewell Cape形成世界最大的海潮,落差高达十六米。相不相信,Fundy海湾退潮所流返大西洋的水量是全世界河流流量的总和。如此潮汐交替,多少年下来,也形 成一些独特的奇景。Hopewell Cape的岩石便是其中之一,另有St.Martins的海洞和Saint John的倒瀑布都是我们以后的旅游去处。也是由于Fundy海湾的水流的剧烈搅动,吸引了多达15种类的鲸鱼群每年夏天来此狂吞被潮水搅起的鱼虾。因此 我们也在此行程中安排了观鲸。

早晨七点钟就起了床,睡眼惺松地赶往Hopewell Cape,因为那儿8:45正是最低潮,沿岸的岩石都暴露出来。这儿最出名的是Flower Potted Rocks(花瓶状岩石群),三、四层楼高,下细上宽呈花瓶状,上端还生长枞树、云杉等。潮水涨满时,看之似生长植物的小岛;退潮时,游客们就可在长满水 草的海床上漫步,欣赏这些海边石林。

中午和观鲸船的老板娘打电话,知道船票没问题了。于是我们立即赶往270公里开外的Black Harbour(黑港),从那儿搭5:30的渡轮往观鲸船所在的小岛Grand Manan。下午四点半赶到港口,车子等着排队上船。渡轮很大,能容纳64辆车和300名乘客,是我坐过的最大的轮船了。航行了一个半小时,抵达 Grand Manan岛,此岛以观鲸旅游出名。之后驱车往岛上的Anchorage省立公园,这儿有岛上唯一的露营地,条件不错,树丛及灌木把各个帐篷地分隔得很 好。



观鲸船“Against the wind”号并不大,有顶蓬,由原来的渔船改装,船长也就是原来的渔夫,有二十七年丰富的航海经验。12:20我们出发了。共二十二名乘客。一个半小时后 开始进入鲸鱼区,船慢了下来。海面平静,只有一些海鸟在飞翔着,降落后凫于水面上。远处有只海豹露了两下头之后也消失了。人们还在安静地观察着四周,忽然 一声惊叫,“在那儿!”,观鲸船立马转向加速,向那只鲸鱼靠近,然后慢下来等待。周围有两只鲸鱼,可能是一对情侣,一直相伴而行。

平静的水面上突然“哗”的一声,一条几米高的水柱喷了出来,然后鲸鱼露出头,带鳍的背,最后是尾部,慢慢地翻转身体扎个猛子又潜下水去 了。过了几分钟,又从另一个地方冒了出来。据船长说,鲸鱼的呼吸方式至今还是个科学家们未能解开的谜。我们的船就或远或近的跟着这两只鲸鱼,最近的时候可 以清晰地看到它们头上的两排疙瘩和出水孔,甚至有两次它们从我们的船下潜过去。老外们兴奋地惊叫着,“Oh,Jesus!”。追逐了一个多小时,在两只情 侣鲸鱼给我们做了一次配合默契的潜水表演后,我们返航了。四点半抵达港口,结束了我们的观鲸之旅。

鲸鱼大者可达二十多米长,七、八十吨重。不过我们见到的只有大约五米长。小时候连环画里读过一个有趣的关于鲸鱼的故事,主人公连船带人都 被一只鲸鱼给吞进去,然后他就在鲸鱼肚子里的一个凹洞里生活了一阵子,点着蜡烛,摆张床,桌椅什么的。我还知道关于鲸鱼的知识就是鲸鱼不是鱼,是哺乳动 物。还有鲸鱼是不长牙齿的,颌部长有几排鬃毛般的大刷子,用于过滤海水。

赶往轮渡码头。由于大雾,轮渡晚点了二十五分钟,所以我们还来得及赶上原本5:30的班次。海上雾重,轮船不时鸣响汽笛。我们在船上享用 了晚餐。七点半时,抵达了码头,马不停蹄地我们冒着浓雾开往七十公里开外的Saint John(圣约翰市)。圣约翰市依山傍海而建,路曲折多坡,我们按帐篷地的标志牌拐了不知几十个弯,终于到了Rockwood省立公园。它自称是唯一的有 露营地的城市公园,露营地很大,有二百多个露营点。管理员热心地按普通营地的价钱给我提供了一个有水有电的营地,他挤了挤眼算是一种心领神会。我们将在这 个营地住三晚。



愁云惨淡,阴霾密布,凄风冷雨,这就是今天的天气。不过,真正的旅游者应该是能做到不被天气影响的。我们先去Reversing Fall(倒瀑布)踩点,因为它只在涨潮时潮水快速猛烈倒推St.John河的入海口时才出现,今天是下午4:15。在那儿的Info Center要到了圣约翰的城市地图,在此之前它对我们还只是一座迷宫。路上见到远处海港中泊着一艘超巨大的远洋邮轮,于是扑过去观赏。它外观模仿鲸鱼, 扁嘴圆头白身子,红色的烟囱作鱼尾状,船体至少十层楼高,尺度与周围的建筑比起来,简直是个庞然大物。圣约翰市是大西洋沿岸最活跃的海港之一,特别是它的 深海石油终端,能处理一些世界最大的船只。


看完大轮船,找洗手间时无意中进入Market Square,北虹愁眉略展,更妙的是sky-walk(有顶天桥)把商业综合体、历史古迹、港口等连为一体,于是我们便开始了雨天室内逍遥购物游。先参 观了免费的新布朗斯维克省博物馆,它的收藏比起安省博物馆要逊色许多,安省博物馆可是把埃及木乃伊、中国汉代古墓都给搞来了。值得一提的是Old City Market(老城市广场),1876年建造的农贸市场,内部模仿翻转的轮船壳体。里头一排排的摊位摆放着蔬菜、水果、海鲜、肉类、工艺品等等,人群熙 攘,热闹非凡,这种情景在加拿大还是第一次见到。Market Square艺术氛围较浓,有一些很好的工艺品店。


五点半时回到营地,我们在霏霏细雨中抗洪救灾,在变成洼地的帐篷里用毛巾往外拧水。回来前在Market Square的Info Center得知明天的天气情况是“多云渐晴,60%下雨机会”,北虹开始默默祈祷,“Tomorrow is another day”。


阴有雾。我们去St.Martins玩,距圣约翰39公里。先参观了一个灯塔和一对Twin Covered Bridge(孪生廊桥),附近退潮后的渔港中,船儿们都静静地蹲坐在沙滩上。接着去看附近海滩的海洞,沙滩上石头满目,又捡了一会儿。洞前有一条小溪挡 路,游客们纷纷脱鞋,涉水而过,再濯足穿鞋。

去一条最近开发的十六公里的风景线Fundy Trail,分别设有汽车道、单车道和徒步行走道。公路在高地上延伸,右侧山谷里云雾缭绕,Fundy海湾的海水在脚下几十丈处隐约可见,空气中有种仙花 异草的奇香,提神醒脑。这儿就仿若是蓬莱仙境,驱车前行时,大团大团的雾从车旁飘浮而过。途中顺着由绳索和木条结成的云梯拾级而下,观赏了一个瀑布 Fuller Fall。最后Fundy Trail通到一个山谷,雾锁群山,内有一条叫做Big Salmon(大三文鱼)的河流,河上有一悬索吊桥。沿河而行,水面豁然一宽,大海乍现在眼前,水雾弥漫。虽然没有吃午饭,可呼吸着这天地灵气,也不觉得 怎么饿。



7月31日 (第十九天)

烈日当空。快离开Saint John时,才见到它的阳光。北虹的计划是经由美国返回多伦多,顺路玩几个小景点。开了一个小时就到了美国海关(St. Stephen),排队等候了近一个小时。因9.11恐怖袭击的影响,进入美国的车辆都有军警人员打开行李厢检查。过了海关即进入Maine(缅因州), 属东部时区. 路边的住宅大都飘扬着星条旗,这也是9.11后的新变化。

美国的油价每加仑1.4美元,相当于每升$0.57(加币),在安省是每升$0.7,而大西洋诸省则是每升$0.8。 油价与经济发达程度成反比,真是个奇怪的现象。据说美国享受着全球发达国家中最低的油价。

下午抵达Acadian National Park,天热且游客多,而景色一般。一块小小的沙滩上沙丁鱼般挤满人,令人怀念P.E.I.美丽空旷的海滩。

这个公园唯一略可一提的景点是Thunder Hole(雷洞)。 涨潮时在海水的挤压下,此洞先喷出一道水柱,再发出“呼噜呼噜”声,好象一头怪兽在里头喘气。

逗留了三小时,傍晚时离开此公园,往计划中的二百多公里开外的露营地而去。夜已深时,我们还在荒山野岭间驱车,四周漆黑,北虹开始害怕,虽 然天黑前她还挥舞着拳头说要take the challenge。九点半才抵达,管理人员已无可寻觅。我们自己摸着黑找了块地,搭好帐篷。在给气垫床充气时,马达声终于把管理员引来了。

8月1日 (第二十天)

进入New Hampshire州的白山地区(White Mountain National Forest)。天气依然很热,这一带却是美国佬的度假胜地,不知他们是来玩什么的。除了爬山,经过的小镇有许多人工修饰的东东,交通拥挤,游客众多。 一条小河上有许多大石,无数人于其上玩耍,活脱脱一幅群猴戏水图。唉,可怜的美国人。

中午二点时,抵达了小镇Lincoln(林肯)。群山围绕的山谷中,阳光暴烈,热浪滚滚,和太上老君的炼丹炉恐怕差不多。这儿就是北虹计 划中今天的目的地,下午和明天原打算去附近的山峡玩儿。可是在加油站用过午餐后,难忍热浪酷刑的我们决定撤退,三十六计,走为上。算算这儿距多伦多也只有 八百五十公里,索性连夜逃回。

路边见到了“OLD MAN”,远处山顶凸现出一个天然的老人側头像,它被称作New Hampshire州的标志。下午驱车在烈日下疾驰时,北虹言道她眼前不时浮现出Fundy Bay海滩上那条冤死的怪鱼。在一个加油站看到今天的温度是35摄氏度,估计我们的老爷车内不下40度。

过了海关,进入魁省,这回是以加拿大公民的身份进入的。六时多时到蒙特利尔,10号->15号->40号->20号, 我们飞离这座复杂的城市,好似一艘飞船逸出庞大的基地进入茫茫宇宙。公路笔直平坦,荒野漠漠,而车里的我们则咽着口水在讨论今晚回去后到那家中餐馆,点什 么菜了。

终于回到安省。401公路上车灯如梭,这是北美最繁忙的一条高速公路,也是安省的大动脉,这儿你能感受到它的动力和心跳。401公路由四 条车道变为六条,而后又变为十六条,愈行愈宽,百川汇海。午夜十二点,当我们缓缓的驶下高速公路时,收音机正放着熟悉的老歌“Forever Young”。


我们回来了,带着大西洋的海腥以及魁省的交通告票。那P.E.I.的沙滩、苍鹭、落日, Nova Scotia的灯塔、骇浪、巨石, 以及New Brunswick的大潮、石子、鲸鱼都给我们留下了深刻美好的印象。此次行程共七千五百八十四公里,途径加拿大五个省,美国三个州。


Year 2002: Go East -Nova (2)


半夜就下起雨来,还很大,未曾停歇。每次醒过来,便听到帐篷上涮涮的雨声以及北虹的叹息声,迷迷糊糊地就又睡着了。早晨实在憋不住了,另外被褥也被渗进来 的水浸湿了一角,无奈起床。披上雨衣时想起Michael那温暖的笑容,这是临行前她送给我们的台湾雨衣。可气的是,等我们收拾好,用过早餐,准备上路 时,雨却停了。

过跨海大桥,交了$37.75买桥费(双向),又重新回到新布朗斯维克省,然后很快便进入Nova Scotia(新斯科舍省)。北虹有些发烧,额头发烫,可能是早晨给雨淋了。

傍晚抵达位于Cape Breton Island上的城市Baddeck,它是新斯科舍省著名的300公里环行风景线Cabot Trail的入口处。登记了两天的露营地,在营地的餐厅吃了炸鸡块和热狗后,北虹的烧全退了,食物和睡眠对她来说便是良药。Baddeck这一带是高地, 海拔较高,晚上有些寒冷,呵气可见。



Baddeck原本是造船中心,后来一些苏格兰裔纷纷到此购置夏日住宅以解他们的思乡之情,因为这儿的风光很象他们的老家苏格兰,发明电话 的Alexander Graham Bell就是其中的一个。Nova Scotia 在拉丁语里就是“新苏格兰”的意思。这个省和苏格兰很有些渊源,从门口挂的木轱辘到省旗都可看出。

Cape Breton Highlands National Park(布莱顿角高地国家公园)就位于Baddeck北部。早上十点出发,晚上十点才披星戴月地赶回营地。只用一句话就可形容此日游玩的感受,那就是仿 佛坐了三百公里的过山车。上坡下坡,左右盘旋,不时地咽口水以减轻耳压。北虹的神经受到了很大的考验,一直紧拉着悬挂在车里的毛巾。

300公里的Cabot Trail环行贯穿整个布莱顿角高地国家公园,也是我们的行车路线,由西门进入,一路上景色平常。公园西临圣劳伦斯海湾,东向大西洋。公路于崇山峻岭间盘 旋,海水在脚下几十丈处。远处的海平面看起来仿佛是竖立着的,夹在山谷间,高峡出平湖的感觉。行进中,弯入一陡峭碎石土路,抵达一瀑布Beulach Ban Falls,非常美,水虽不大,却从高处岩石如玉珠般层层跌落,如江南采莲女子的纤纤素手。下端流成清澈见底的小溪,日光碎碎地洒在林荫里。

车行入东侧的Cabot Trail,日已渐坠,出现了一些小海湾,景色不错。海浪在夕阳下拍击着海滩边的乱石,海鸥声声,岩石上一人孑然独立,乱发飞扬,神情萧瑟,眺望远方,当然这不是杨过在等小龙女的镜头,乃敝人在拍照留念也。


路边经过一个超市Co-op Basic,北虹进去采购了些方便面、罐头、水果。这次旅行带的粮食不多,来时听说海边龙虾极便宜,想象中一队队龙虾正排着队等着我们来吃呢。哪知不是这 么回事,每天都只能吃当地的小饭馆,$15-25一顿,象麦当劳、Burger King这些便宜的快餐连锁店通通儿不见。老外的食物太简单,不外乎各种三明治,菜汤,土豆条。本以为贫困省份物价应该比较便宜,实则不然。这儿的汽油和 露营费用都比较贵。

中午抵达Fortress of Louisbourg(路易斯伯格要塞)。这是个国家历史公园,也是加拿大现存的最大历史要塞之一。路易斯伯格兴建于1713年,是法国在大西洋的重要战 略要地,也是一个繁忙的海港,后在英法交战中,几度易手,最后于1760年英方将其要塞摧毁,并将其居民驱逐回法国。之后,路易斯伯格便消亡于世界舞台。 至今居民仅1200人,而当年英国相似的战略要地Halifax,如今已发展成为一个现代化海港城市,人口十一万。

1961年加拿大决定在遗址上重建五分之一的路易斯伯格,将其开发成为一个遗址历史公园。提议的最早动机很简单,为解决一批失业的煤矿工 人的就业问题。工程师们按照当初的一些图纸记载,以及历史学家们的考古研究,非常严谨地重现了一部分当初的路易斯伯格。大至要塞规划,房屋地界,小至门 锁,烟囱式样,无不按照考古挖掘的发现复制。另外,一个重要特色就是一批costumed animators,身着当年服饰,有军官、士兵、商人、仆人、修道士和普通百姓,生活在1744年的夏天,游客们可以同他们随意交谈。

公园的巴士把我们从Vistor Center带进海边的要塞。我们参观了防御工事、火药库和一场毛瑟枪、加农炮的表演。士兵队列前有两个吹笛手和一个鼓手,笛声和着鼓声,悦耳动听。而士兵们则按着鼓点迈着一种笨拙缓慢的步子前进。

要塞里有三个小伙子吆喝着叫卖刚出炉的面包;一些妇女儿童在玩着一种掷球游戏;另有二、三人拉着几只绵羊逛着。我们走入一些房子,里面一部 分布置成当年的模样,另一部分则布置成展览,展示考古出来的东西,重建工作或播映一些有关的录像。路易斯伯格被很严谨地按当初的情景复原,每栋房子都有当 初主人的名字以及从事的职业,他们的基本情况也可通过同你遇到的那些二百五十年前的人们的交谈中而获知。

当我们走入其中一栋房子时,客厅里坐着一位胖妇人在绣东西,原来这是一个工程师的家,而她则是他的客人。夫人和八个孩子此时正在法国, 记住,这是在1744年的夏天。此工程师设计和规划了这里的政府、军事建筑,是个重要人物,因此房子也很大。再往里走,有一个厨娘在厨房里忙碌,她向我们 展示了早晨煎的鸡蛋饼。我注意到壁炉边有个奇怪的机械,经介绍后才知道原来是个烤肉机,一块缓慢下落的石头带动着许多齿轮转动,进而使得一根铁杆在火上均 匀地旋转,复杂而精巧。

走回街上,听到一阵急促的鼓声,这回是游街宣判一名犯了偷窃罪的士兵。当初各种活动均由相应的鼓声作为信号召集大家。回头看到一个衣着光 鲜的家伙在街上闲逛,似乎有些与众不同,我们猜测或许是这儿的上尉或将军,于是上前攀谈。意外的是他竟然是名囚犯,因为是英国贵族,当时享有这种特权,不 必被关在囚牢里。







朝Halifax(哈里法克斯),新斯科舍省的省会前进。下午三时,乡村歌曲一下切换成震耳欲聋的摇滚乐,高速公路纵横交错,熟悉的都市气息扑面而来。我们从哈里法克斯边上掠过,目的地是著名的景点Peggy's Cove,一个艺术家和摄影家们钟爱的地方。

搭好帐篷后休息了一会儿,六点时我们去Peggy's Cove。接近时见漫山遍野散落着大大小小的石头,仿佛天上落下的陨石群,大若风车,小可盈握。Peggy's Cove是新斯科舍省崎岖多岩石的南部海岸线上的一个小渔村,一座灯塔耸立在海边的巨大花岗岩石上,以及一些小巧的码头,使得这里扬名。灯塔附近的一带大 多是有裂缝的巨大花岗岩石,错落杂置,而海风猛烈,北虹匿身于大石间的凹处避风观赏,我则踩着岩石前往观潮,浪高三丈,扑面打来,眼镜布满细小水点。落日 透过云层,露出灰蒙蒙的光,天地空茫一片,海潮汹涌奔腾,大风中仿佛听到齐秦的歌“。。。风暴渐渐升高 大地开始动摇 我在风中呼唤你听见了吗。。。一直到世界末日 。。。”。


Year 2002: Go East - P.E.I (1)



而今年,暴风雨虽还未过去,可是生活还得继续,从洞穴里探出头来的人们发现自己还苟活着,庆幸之余,一个个呼朋引伴, 挈妇携雏的出外游玩。北虹的公司稳定,我也混了个研究生名额继续深造。于是便筹划起了这回的大西洋诸省之旅,去New Brunswick(新布朗斯维克省),P.E.I(爱德华王子岛省),Nova Scotia(新斯科舍省)观海潮,吃龙虾,看鲸鱼。生活该是多么美好啊。



7月13日 (第一天)



日行八百公里,到达露营地Lac-Louis,距Quebec城六十公里。管理人员只会一点英语,他们总是说法语。我说对不起,我不会法 语,他笑眯眯的说:"OK",然后叽哩咕噜的又来一串法语,不过这回连比带划的了。幽静的营地,在一个半山岗上,很久没有露营了,感觉真是不错,只是周围 都是说法语的鬼子们。

7月14日 (第二天)

阴天,正是开车的好天气。听着乡村音乐,啜着龙井茶,在广袤的北美大地上纵车奔弛,心旷神怡。哼,时速120km的车子只配在慢车道上爬。 漫不经心之间,忽觉后脑勺有些冷嗖嗖的,一瞄后视镜,啊,一辆警车正贴着我的车屁股,急向右线变,它亦跟着变,无奈停下,结果是罚款$210,加罚三点。 说我速度135km/h,超速35km。回想当初殊不恭敬的笑指那些路边的超速罚款告示牌 (牌上对各种速度都明码标价),真是悔之晚矣。想我在安省纵横驰骋两年有余,从未失手,此次一进入魁北克,就尝到了魁省人民交警的铁拳。

有了案底在身,当然不敢放肆了,在慢车道上老老实实地学蜗牛爬的时候,心中最期望见到的镜头当然就是警车抓人了。这个愿望一直到下午,见 一卡车被警车撵着追时才实现。这且按下不表,不能让它坏了吾们游玩的兴致。北虹也一直宽我的心,虽然她也很心疼。因为后来当我轻松起来之后她反倒不时嘟囔 两句了。

下午1:30进入了New Brunswick(新布朗斯维克省),185号公路转上2号路。这是加拿大唯一的一个官方英法语省份,属大西洋时区,与多伦多的东部时区有一小时的时差了。离开了国中之国的魁北克,松了口气,因为它的路牌,甚至所有招牌都是法语。

上了2号路,便开始了River Valley Scenic Drive(河谷风景线),沿St. John River蜿蜒行进。St. John河是新布朗斯维克省的母亲河,相当于圣劳伦斯河在魁省的地位。附近民居的房子屋顶坡度大,且大多附有顶部呈四折式或半圆形的谷仓,自然此地以农牧 业为主。当年北美独立后有七千余名保皇党人移居于此山谷中。

第一个遇到的景点是The Falls and Gorge,于St. John河上端,落差23米,水量惊人,每秒600万升,是尼亚加拉瀑布的0.9倍。

黄昏时微雨,抵达第二个景点Hartland Covered Bridge,世界上最长的廊桥,391米。横跨St. John河,建于1921年,侧廊可行人,中间通车。新布朗斯维克省的人们有个传统,每次穿过St. John河时都许个愿。于是北虹过廊桥时便闭上双目,念念有词。当初建廊桥的原因很多,其中一个有趣的原因是防止牲口见了下面湍急的河水惧怕而不敢过桥。 因为廊桥的外观看起来象谷仓,使牲口们感到亲切。此桥亦以"kissing Bridge"闻名。

晚上于St. John河畔的Sunset View露营,洗了个投币热水澡。只是靠着2号路,车声喧哗。

7月15日 (第三天)

继续昨日的行程,在新布朗斯维克省的首府Frediction迷失了一段路,转入Miramichi River Route北行。开着车蛇行于山林碧水之间,小雨零星。Miramichi River 是世界上著名的钓三文鱼的河流,每年秋季,大批母鱼儿们从大西洋沿此河逆流而上产卵,然后死去。道上车辆稀少,偶有几辆满载木材的货车。出卖木材资源应是 此地人民主要的生财之道。

依图索骥去找一条新布朗斯维克省最长的悬索桥(200米),Princeville FootBridge。雨越下越大,费了一小时,仍毫无所获,亦无人可问讯,只得离开。路上经过一个个小镇,那些住宅大都小且破旧,教堂永远是最显目高大 的,而墓地很多,墓碑旁大都供着鲜花。似乎每个小镇都只由这三样组成:住宅、墓地和教堂,分别讲述着生命、死亡和信仰。可是没有饭馆,我们两个凡夫俗子饥 肠辘辘,一直到了三点多抵达Miramichi城,才在一家Pizza Delight吃了顿美味的午餐,PASTA做的很棒。


去走了两条小径: 海狸小道和盐碱滩小道,都铺设了栈道以保护生态。这个国家公园是以其保护珍稀动植物而出名的,可是对吾们来讲是动物见不着,植物看不懂。各物种们在这儿自 生自灭,物竞天择,按照食物链生活着,不过我怀疑现在游客也已加入此食物链了,成为蚊子的上一环。这些小径里蚊子极多,于是象北虹,便几乎是张牙舞爪的在 前进了。

虽然受到蚊子们的打击,北虹还是不气馁,因为在这公园的Kelly海滩上,有一条长长的栈道通向大西洋边的沙洲,那儿常有海豹们嬉戏玩 耍。可是,原来,但。。。你必须走六公里沙滩才能见着,来回十二公里啊,只得放弃。回营地时,却无意中发现两只黑熊过马路,一远一近,过去后在树丛里站起 来,回头一望,然后离去。


7月16日 (第四天)

上了Acadian Coastal Drive, 在大西洋边驱车前行。途经Irving Eco-Centre,这是北美东北海岸仅存的大沙丘,沙洲如纤细的手指伸进Northumberland Strait(诺森布兰海峡),上面铺设了二公里弯弯曲曲的栈道以保护生态。我们漫步于栈道和沙滩,呼吸着大西洋上方自由的空气,权作健身加开胃,因为我 们的下一站是号称世界龙虾之都的Shediac城,以捕捞、加工龙虾以及举办龙虾节而著名。

胃里打着小鼓点,我们向着龙虾进军。七月初在Shediac有龙虾节,可惜已经错过。镇入口处有一大龙虾雕塑,这是世界上最大的龙虾,重 达九十吨,十一米长,五米宽,五米高,每年三十万人慕名而来。接下来自然是去享用龙虾了,挑了一家饭馆,要了一个水煮龙虾和一份龙虾PASTA,水煮龙虾 要$28。女侍者长得象席琳.迪翁,当我嫌贵时,她恐吓我说在法国吃一只龙虾需一百多美元呢。龙虾味道其实一般,肉既粗又老,不若温哥华大闸蟹多矣。

饱餐一顿之后,我们启程前往Prince Edward Island(爱德华王子岛省),经过跨海大桥(Confederation Bridge),桥长13公里,费时10分钟。这是由新布朗斯维克省和爱德华王子岛省两省合力于96年建成的,从此把这个孤岛与大陆连接起来,给岛上人们 的生活带来很大的方便。五千余名参与工程的人员以及公司的名字都被铭刻在Gateway Village入口的砖地上。

于Blue Heron Scenic Drive(蓝苍鹭风景线)上前往露营地Cabot省立公园,途中见到一个美丽的小渔港。



中午到达Cavandish(卡文迪许),这儿出过一个大名鼎鼎的加拿大女作家L.M.Montgomery (不过来前我还不知道她),写了系列小说“Anne of Green Gables”(绿色山墙之安妮),塑造了一个维多利亚时代的红发女孩安妮,类似简.爱,汤姆.索亚的一个人物。卡文迪许因此有一系列的与小说有关的景点 被保存或兴建,象Green Gable Village,Green Gable House,Rainbow Valley等。在绿色山墙村的礼品店买了这套书,共八册,$48,以作纪念。

在爱德华王子岛国家公园登记了两天的露营地。此公园狭长分布于海岸线,且被海湾截为三部分,分别位于Cavandish, Stan Hope, Greenwich三个城市,我们在Stan Hope露营。



去公园的Greenwich(格林威治)部分玩,四十公里车程。走了一条靠着St. Peter海湾的小径,它穿越了旧农场荒地,森林,沙丘,池塘和海滩。

旧农场荒地原是属于叫Stansion的一家,几代人在此沙地上耕作,种植干草、小麦和土豆,养些牲畜。现在已不留什么痕迹了,只有一些当 年的黑白照片,生活应该是很艰辛的,最后一代Crisp Stansion掌管农场时,年仅十四岁。关于加拿大以至北美的农牧业,值得一提的就是它的干草是当作粮食一样种植的,现在更是实现了现代化的灌溉及收 割,捆捆干草卷随处可见。比起国内牧民们还只能完全依赖大自然,经受旱灾、雪灾之苦,这儿的牲口们应是比较幸福的了。

小径的池塘部分修了漂浮栈道,风吹过时便摇摇晃晃,北虹心惊,不敢动弹。我们对dunes(沙丘)也有了进一步的认识。一般沙地是随着风 的作用而改变形状的,一些植物象青苔、滨草最早在沙地上定居,然后其它的植物们也开始生长,一起协助沙丘稳定下来,鸟儿们也随之开始出没,这就是海边独特 的沙丘生态系统。若说峡谷是水与石斗争的结果的话,这儿则见证了风与沙的搏斗。


在一个午餐吧吃了午餐。下午回到营地,下起雨来,于是休息。傍晚时天晴了,想去海边吹吹风。无意中却见到了辉煌壮观的海上落日,气势磅礴, 变化万千,造物主毫不吝啬地向我们演示了它的神奇,言语难以描述。回营地路上,红狐狸到处可见,并不畏惧人,看起来并无多少狡诈样,甚至有些憨。在加拿 大,人与动物之间已建立了一种信任感,连狐狸都对人们信任有加。




P.E.I.整个岛就象是个大高尔夫球场,微有坡峦起伏,大片大片的草地修剪得整整齐齐,房屋整洁干净。它四面环海,水土肥沃,是个农业省 份。这儿气候变化快,说不定什么时候头顶上空海风就吹来一片乌云,然后哗啦啦地下起雨来,而顷刻间又会阳光灿烂。这个岛的特色是红色土壤以及海边的红砂 岩。蓝苍鹭是这一带的代表动物,我们在该省的旅游线路就称为蓝苍鹭风景线。在路弯的一个海湾边见到了两只,可是它们太警觉,比傻狐狸警惕得多,稍一接近就 飞了。苍鹭站姿优雅,飞翔的动作则更是美到极致,轻盈舒展,比起来海鸥只能算是乌鸦。此情此景让人想起那名句,“落霞与孤鹭齐飞,秋水与天共一色”。一个 疑问是,蓝苍鹭不蓝,红狐狸不红?

中午时到达Charlottetown(夏洛蒂镇),是P.E.I.的首府,当年按法国乔治三世的老婆夏洛蒂的名字命名的。夏洛蒂镇被称 为是加拿大的出生地,因为当年为讨论是否成立加拿大联邦的会议是在这里举行的。我们在夏洛蒂镇的老城区步行了一圈,徜徉于十九世纪的古老建筑中。天主教堂 St. Dunstans Basilica是在来自巴黎、纽约、芝加哥等地的艺术家的指导下完成的,石头是从Nova Scotia运来的,内部大理石柱、拱形天花顶、彩色玻璃,金碧辉煌,奢侈豪华。比起来基督教要朴实寒酸得多。他们的差别就好象丐帮中的净衣派和污衣派, 虽然都靠圣经讨生活,却也泾渭分明,互不服气。十九世纪时有两兄弟William Harris和Robert Harris为夏洛蒂镇的魅力贡献颇多,William是建筑师,设计了一些哥特风格的教堂、公共建筑和房子,喜用当地粗糙的红砂岩,Robert则是画 家,两兄弟曾合作完成了一个小礼拜堂。


Year 2000: Go West (5)

八月二十九日 ( 第十九天 )

阴雨天,群山云遮雾罩。我们离开 Jasper踏上归程。计划是经 Edmonton到Calgary(卡加利),再进入美国 境内,随原路九十号州际公路返回。

Edmonton是阿尔伯特省的省会,人口六十余万,最早始于皮毛贸易,后因淘金热而迅速膨胀,现在则是 以石油、天然气等工业为主。我们在 Western Edmonton Mall玩了会儿,它号称是世界最大 Mall。 此 Mall除了有众多商场、美食广场,还有室内游乐场、海底世界、溜冰场等娱乐场所。 就象恐怖电影的目的就是想让人不舒服 一样,游乐场的娱乐设施,除了小孩类的,全是各种各样整人装置, 而人们却是乐此不疲,说明人类具有某种喜欢受虐的天性。生平第一次坐了过山车,每转完一圈就想该 结束了吧,可马上又来一圈,令人痛苦不堪,真是花钱买罪受啊。

Mall里还有免费海豚表演,海豚生就笑眯眯的憨相讨人喜欢。音乐声中随口令起舞,猛然跳至空中七、 八米高,翻转三周半入水。最绝的是挺立起来,迅捷地后退,优雅之极。

北虹在一家店里买了一件毛衣以作纪念。我们离开了 Edmonton继续前进。五、六时进入 Calgary,道路 复杂,城市庞大,路边可见密密麻麻蚂蚁窝似的住宅群,跟流水线上一个模子里脱出来的一样。 Calgary 有牛仔城之称,但现在主要是石油化工业,成为阿尔伯特省第一大城市。

路上一直下雨,因此不能露营了,问了一家 Motel(汽车旅馆),要七、八十元一夜,我们就继续行进了。 天黑雨大,九时到达 High River,麦当劳用过晚餐后,在一个很偏僻的角落找了家 Motel,只须五十加元。 洗了澡,躺在席梦思上看了会儿电视,就很幸福的睡着了。

八月三十日 ( 第二十天 )

开了大半上午在 Coutts小镇过了边境进入美国蒙大拿州,交了 12美元初次入境费 (进底特律海关时却 是免费的,因为过于拥挤,忙得没时间收钱 )。

中午在Ulm小镇爬下高速公路小寐,醒来发觉不妙,车子发动不起来了。原来睡觉时忘了关车灯,把 电放光了。打电话向 CAA求援,报告了位置。 CAA(加拿大汽车联合会 )和AAA(美国汽车联合会 )是伙伴 关系,所以我们这些 CAA会员在美国境内出事 (roadside emergency),AAA就会提供救援。不到二十分钟, AAA的救援人员就赶来了,那个美国佬表情冷淡,二话不说,帮我们发着车就扬长而去,不如 在多伦多 叫过的CAA,那些加拿大佬们嘘寒问暖,态度友好,还让我们的电池吃得饱饱的。

晚上在Missouri(密苏里)河畔的 Wolf Creek露营。密苏里河是通往西部的水上高速公路,每年有无数 船只由此进入西部。露营地相当大,人却很少,躺在帐篷里的一个发现是又听到了草虫子的 呢哝声,久违了。

八月三十一日 ( 第二十一天 )

今天主要是赶路,走完了广阔的蒙大拿州和怀俄明州。晚上的露营地就在 90号高速公路边上的 Sundance小镇,已到了怀俄明州和南达科他州的交界。

九月一日 ( 第二十二天 )

连奔四州,傍晚时北虹在查找伊利诺州的露营资料。我突生一个新想法,想连夜赶回,我喜欢这样的 结局。以前绘制工程图时,最后我总是以熬夜而收尾。北虹很激动地同意了,并睁大了双眼。

晚上到达 Chicago(芝加哥),高速公路纵横交错,比多伦多复杂得多,走错了一段路,扔了一通硬币 才回到正道上。十点多钟在加油站加油时,见几个彪形黑大汉骑着摩托车轰鸣而来,斜眼一瞟,只见 一汉腰后插把手枪。望望四周也多是黑人,北虹吓得连厕所也不敢上了,连忙对我说,赶快离开这个 是非之地吧。美国的犯罪率高,其实主要是在纽约、芝加哥这些大城市,其它地方特别是小镇,则 完全是另一码事,安全宁静,田园风光,人们友好善良,富有人情味,就如 <<廊桥遗梦 >>里一样。

九月二日 ( 第二十三天 )

过了芝加哥,已进入九月二日。在 94号公路上沿密歇根湖 (五大湖之一)前进。浓雾弥漫,黑漆漆的 夜里车辆稀少。由于可见度低,北虹一直很警觉,不时地提醒我不要睡着。但在 Lansing还是走错 了一小段,应该拐上 69号却上了96号,开了几十公里才发现。四时在 Seven-Eleven买了两个早餐 Pizza和咖啡,热乎乎的吃了很舒服。五时抵达 Port Huron海关, 关员是个印度裔姑娘,睡眼惺忪,头一点一点鸡啄米似的。

雾依旧很大,这回在加拿大境内了。从 402号高速公路拐上 401继续前进。七点时非常困,在一个农 场边上打了个盹,还是困得很。睁开眼,突然发现薄雾中几只奶牛正盯着我。

好象梦游一般,在 401上开着一百多公里的速度。十点时终于到达了多伦多,我们回来了。一日一夜 24小时连开了二千四百公里,从美国中部直接狂奔回多伦多。十一点时我们已经坐在“ "三和"烧腊店里吃粥了。


这次行程共计一万二千六百二十七公里,途经美国十个州和加拿大三个省,深深地体会到北美是一块 美丽富饶,文明昌盛的乐土。一路上在北虹的督促下,坚持在露营灯下写完了这篇游记。献给我们的 朋友们分享以及作为我们自己的纪念。

Year 2000: Go West (4)

八月二十四日 ( 第十四天 )

打算今天把 Banff镇周围的景点都扫荡掉。

首站 Hodoos,只是立着的几方瘦瘦的怪石。多少多少年以前本是悬崖的一部分,慢慢的悬崖风化消失, 只遗下这一些最坚硬的石头。来历虽然玄妙,景却不动人,聊且一观。 然后去 Bow Fall, 却是找不着, 只见下方湍急宽阔的 Bow River,横看竖看不知 这瀑布在何处, 至今仍感疑惑。

Cave and Basin 还不错,是一幢设计巧妙的建筑。里面藏有一个大洞穴,洞中又有泉水。一层 还有一大一小两个院子。小院内是一汪泉水傍于山下,池中冒着气泡,硫磺味极重,不可久呆。此池中 居住着世界上濒临绝迹的班芙蜗牛。大院内有一长方形水池,源于泉水,院子围墙设几扇大玻璃窗, 可见外边山林景色。

Banff 镇上热闹熙攘,多为游客。在街上走路,抬头便可见屋脊上露出一角雪山。 下午天气转阴,乌云翻滚。我们乘缆车上琉磺山,俯瞰 Banff 镇全貌。一弯河水 Bow River 绕镇而过,中间还有一圆圆的土山 Tunnel Mt.,四周群山环抱,颇符合中国的风水之术。

八月二十五日 ( 第十五天 )

今天实为玩落矶山脉的重头戏。它自称集五十个瑞士于一体,真是如给我的初次印象般乏味吗? 今天便可知分晓。旅程计划是大名鼎鼎的 Louise lake(露易仕湖 ),Marine lake (慕莲湖 ), Marble Canyon, 和昨天未玩成的 Johnston Canyon。其中 Marble Canyon 是 Kootenay国家公园的代表, 也是我们计划中此公园唯一要去的地方,其余的均属于 Banff国家公园。

露易仕湖一看和照片中没甚两样,两侧高山夹着一冰川名 Victorial glacier, 下面一汪号称是落矶 山脉明珠的湖水。 落矶山脉一般有湖水处均有高山冰川, 相互辉映成趣,冰川融化之水流入湖中。人的 天性是喜新厌旧,喜爱丰富变化而厌恶单调重复的。这湖已在照片上看过多次,现在见了真的却已不能给我 带来多大乐趣,反而去发现它的一些瑕疵。湖水有些浑浊并不十分清澈,不知何故。就象尼亚加拉瀑布, 名气大,商业文化渗入其中,而景色并不耐看。让人深感奇怪的是日本游客极多,多是旅行团。周围全是 叽哩咕噜,老老少少的日本鬼子。黄石公园在世界上应更出名,却是未见日本人,不知为何日本鬼子独钟 班芙。

慕莲湖被印在加元二十元钞票的背面,因而也名气很大。与湖相映的雪山共十座,称十峰山,景色还不错。 有趣的却是它的观景处,须踩着乱七八糟的圆木涉水而过,然后爬上垃圾山才可观其全貌。垃圾山 只是个小山头,黑石错列堆积,观之极似大垃圾堆。游人如猴攀爬其上。置如此丑陋之山于美丽的慕莲湖 畔,真是大自然的一个恶作剧。

让我开始感到欣喜的是 Marble Canyon 。步行于 小径 上,峡谷两侧时有木桥连接,供游人观赏及拍照。峡谷 狭窄,激流其间,水色呈浅湖蓝,冰清玉洁。步行之间,不时有发现的乐趣。峡谷间有 Natural Arch。Trail 之 尽头有一小瀑布,恰似个完美的终结。

中午挨饿玩儿完了 Marble Canyon,开车到了 Johnston Canyon 找了家饭店,已是下午三时,饱餐一顿。 露天茶座下一些游客们在喝冷饮。

Johnston Canyon昨日已匆匆走了一段,觉得无甚稀奇。我本欲不去了,北虹坚持才又去了。不料此 Canyon 实为文章中的大手笔,开篇平淡无奇耳。此峡谷有八千年历史,高160米,每年加深 2厘米。 我们的一生也只是这峡谷变化过程中的小小一段。 八千年水与石的搏斗,留下了峡谷里的奇石激流。石头决定着水的流向与形状,水却在刻划着石头,决定着 它的去与留。以柔克刚,正是大自然的奇妙之处。 小径曲折,高低有趣,游人步行于峡谷半腰中,抬头只可见一角天空。 遇峭壁处,公园修设了 Suspend Catwalk (悬臂猫道),即用钢梁打入山壁内固定的铁桥,望之甚险。走在 上面有些抖动,北虹心惊,手握扶拦,慢慢过之。峡谷内有 Upper Fall和Lower Fall,另还有些小瀑布。 我们误以为 Lower Fall是Upper Fall。出来后才发现错了,为时已晚。 Lower Fall可下观,上观,也可穿过 铁桥钻入一小山洞近观,颇有趣。 Upper Fall据载落差有 30余米。

晚上回到营地,发现隔壁来了一群香港小鸟,叽叽喳喳,热闹得很,半夜还在闹腾。比起来,台湾人安静, 香港人吵闹。

八月二十六日 ( 第十六天 )

今天只可用倒霉來形容。一早即下了小雨 ,天放晴我們就出發了。准备去 Yoho National Park。抬头看 天空,但觉不妙。班芙这边天空晴朗, Yoho那边却是乌云滚滚。

Natrual Bridge 是由瀑布变化而来的。瀑布上部岩石坚硬 ,下层则是松软的石灰岩,被水冲刷而去,水流 则改从下走,形成自然桥。由 Fall-->Natrual Bridge-->Chasum, 这是瀑布自然的演化过程。

翡翠湖(Emrald Lake)是我最喜欢的湖了。湖水是诗意般的蓝,阳光下水中有雪山的倒影。但最好看时 还是在阴天,纯纯的碧蓝。在湖边逛了会儿,我们决定去荡舟。可已经迟了, Canoe已都租出去了, 只得租了条 boat,不如 canoe 那样一人一桨,划起来迅捷,看起来优美。我倒坐船头,专门划桨, 北虹则闲坐船尾。总算歪来拐去把船划到了湖心,觉得没意思。往回划时却忽然下起了雨,夹着小冰雹, 结果给淋成落汤鸡。教训是看起来很不错的事,做起来就蛮不是那么回事了。

中午回到 Yaho 的 village 吃午饭。只有一家餐馆,又适逢一车旅游团的人,排队等候了一阵子。 这是老外旅游团,全是老头老太。一般而言,老外年轻时绝不爱参加旅游团,因为不自在,他们开 RV, 骑摩托、自行车,徒步,各式各样。只有东方人,会有许多年轻人参加旅行团,比起来缺少了冒险精神。

然后驱车前去 Takakaw Fall。此瀑布落差 256米,是加拿大最高的瀑布之一。到达时甚是困倦,天空阴沉, 我们就在车里小寐,睡得甚沉。醒来时不知身在何处,余下口水一滩。高兴的是天又晴朗了,阳光隐现 于云间。便出去观瀑布。其形如孔雀,声若奔雷,端的是壮观。立于山崖下仰观,水雾弥漫。回去时 又逢骤雨,湿漉漉的钻回车子里。

O'Hara 湖原来须走 13公里的 trail 才能到达,只得放弃不去。 时间尚早,北虹说再去一下 Peyto Lake 吧。可这一去差点儿出了车祸。由一号公路上 Ice Field Parkway (93号)公路时急转,车子发生打滑失去控制,头脑却很清楚,猛打方向盘,最后车子扭上了侧边的出口 道,转了180度,北虹也恢复了知觉,大 叫"快煞车呀 !" ,一煞车停了下来。 整个过程几乎是电影里的车技,惊险之至。过了半天魂才回来。

过检查卡后,遇一搭车客,背个大行囊,说是从密歇根 (Michigan)州来的,去了黄石公园、加州、 温哥华,然后到了班芙,比我们的路程还远些,只用了两个星期,且一路全是搭车,晚上睡帐篷,着实 让我们惊讶和佩服,比起来我们开着车算是舒服多了。 后来注意到路边实有不少搭车旅游者,或有伴或孤身。想来他们不应只是图省钱,汽车在这儿是很 便宜大众化的,应是挑战自我的一种方式罢。

这天的倒霉却还未因我们回到营地而结束。发现帐篷口放着个纸条,而桌子上的东西全没了。原来走前 垃圾袋放在桌上忘了扔,被工作人员检查到了,只得灰溜溜的去了办公室,点头哈腰地领回了我们被叫 缴获的物品:电水壶,牙膏和牙刷。班芙公园和黄石公园一样,是熊出没之处 (Bear Country)。 因熊是杂食性的,容易依赖垃圾为生,成为 垃圾熊(Garbage Bear)。甚至袭击人们,成为 问题熊 (Problem Bear)。因此这些公园都实行严格的垃圾管理。

回到营地,天下着小雨,香港小鸟们却是嗷嗷诗哺,围在不大的篝火旁,每人手上叉个鸡腿、鸡翅, 还得给篝火打个伞, 这回倒是安静了。我们钻进帐篷就睡了,帐外寒风细雨。

八月二十七日 ( 第十七天 )

早晨起来洗了个热水澡,出发去 Jasper国家公园,沿 IceField Parkway (冰原路)行进,路边不时可见 大冰川。 十一时到达著名的 Columbia Icefield(哥伦比亚冰原 ),这儿有唯一可乘雪车 (snowcoach)上去游览的 冰川 Athabasca Glacier。哥伦比亚冰原 位于Banff与Jasper的交界处,有一较大的游客中心,依山而建, 内有大厅、礼品店、雪车售票处、饭店等,却是人群熙攘,热闹得很,户外北风呼啸。在快餐厅里吃了 顿中餐,很贵。大厅里有门通到外面的平台,平台上有几个望远镜可看到对面人们在冰川上游玩的情景, 先是用snow shuttle(普通的小面包车 )把游客们拉到山脚,再用雪车拉到冰川中部平缓处。从望远镜里 看冰川山上的游客们躅躅而行,如企鹅般。太多的人上去,冰川看起来很脏,我们觉得上去没多大意思, 而且票价很贵,就决定不坐雪车了。

继续行进进入 Jasper国家公园,先去了 Athabasca 瀑布,这瀑布恰如 "惊涛拍岸,卷起千堆雪 ",其中幽趣 自不待言。有一景名为 "Time Tunnel" (时间隧道), 两壁怪石嶙峋,小道狭窄,有台阶,光线幽暗。千万 年前曾有过一番水石大战,而水失败后撤退改道而行,却留下了当年战争的伤痕。转出时间隧道,登时 眼前一亮,发现身已处一高台上,脚下是极宽阔的滔滔流水,侧边有一红黄色巨大石壁,此处景色若以 "赤壁"名之,毫不为逊。

前去Jasper小镇旁的 Wabasso 露营地登记了个帐篷地,准备住两天。这是个很大的露营地,有二三百个 位置,但发现住客无己, Jasper看来比 Banff荒凉了不少。 下午四时,去 Jasper最著名的天使冰川。一路上早已发现 Jasper的山很独特,均呈深蓝色,远望之象 蓝宝石。天使冰川就在其中的一个蓝宝石山 Mount Edith Cavell上,山路崎岖曲折,不易到达,旅游 大车是无论如何也上不来的,因此跟旅行团来玩的都没来过此地。 Edith Cavell是一战中的同盟国女护士, 在 Brussels (布鲁塞尔 )沦陷时,为照顾伤员,拒绝撤退,同时照顾交战双方数百名伤员,后被德军枪毙。 加拿大为纪念她将此山命名为 Edith Cavell,并将山上的冰川命名为天使冰川。 须走一条冰川小径才能到达天使冰川脚下,山谷中满目皆为石头,荒凉得很。几千年前我们站立处还被 冰川覆盖,但据介绍一片森林正在悄然出现,在荒山乱石中一颗颗幼小的松树正在成长,生命力就在这 山谷中静悄悄然而顽强地体现着。冰川脚下有一小湖,应是冰川所化之水积成,呈前面湖水从未有过的 翠绿色,上面悬浮着一些冰块,都是千年玄冰,晶莹剔透。这是落矶山脉唯一有玄冰漂浮的湖。因山形 陡峭,经常有冰块断裂掉下 , "扑通 "一声,落入湖中。山谷中天气寒冷,海拔三千多米,甚至下起了小雪。 八月雪可谓罕事,但在这山谷中却是平常。

八月二十八日 ( 第十八天 )

今天的计划是 Jasper 的六个湖和一个峡谷,没什么玩头。 Jasper以湖泊众多取胜,可在我看来,湖最没 看头,永远是一览无余。

Jasper这些湖中,值得一写的是 Medicine Lake,每年十月就会干涸。这么大的一湖水突然就无影无踪了, 让印第安人很惊讶,认为有魔法,遂以此名之。科研人员发现此湖下有无数小缝隙,水从中渗走,追踪 之下发现甚至几百里外的水都源于此湖。原来湖下是北美最大的地下水系统之一。平时因雨水充沛,及 Maligine River的水流入,得以平衡,保持湖面高度。而进入九月枯水季节,湖面就迅速下降,到十月 就只剩满目烂泥了,湖面高低可差二十多米。我们看时已大约下了三、四米。

Maligine 峡谷比起前几天的几个要逊色得多,只有一个气泡 (bubble)状的瀑布比较有趣。

下午二点多就玩完了所有的景点,因昨夜很冷,北虹就想提早离开算了,天黑时可开到 Edmonton(埃德 蒙顿),离开落矶山脉,应该会暖和起来。犹豫了一阵,还是没走,回帐篷睡觉去了,白天暖和,北虹 睡得很香。黄昏时,点燃篝火煮面。吃面毕,我去洗碗,突然路边跳出一个黑衣大汉,告诉我有只黑熊 刚进了公园,就在附近晃悠,要我注意些。还说,若有兴趣可去拍照。回来后我又告诉隔壁的家伙, 他有些激动,抖开床单,说要去 "trap bear"。到草丛中站了半晌,什么也没看见。