Download Summary

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Brought to you by ownSky!
Jini™ and JavaSpaces™ Application Development
By Robert Flenner
Publisher
Pub Date
ISBN
Pages
: Sams Publishing
: December 05, 2001
: 0-672-32258-7
: 480
Interest in Jini and JavaSpaces development has greatly increased as developers
discover its power for building network services in a Web services environment.
Jini and JavaSpaces Application Development provides examples of Jini-based
network services, intelligent agents, and mobile computing solutions.
Jini and JavaSpaces Application Development incorporates recent innovations in Java
and Jini technology, including Web services, XML integration, and open-source Java
technologies.
Author Robert Flenner has built, and presents in the book, an innovative Jini-based
application including groupware functionality, workflow processing, autonomous
agents, and B2B integration.
Jini and JavaSpaces Application Development demonstrates real-world usage and
application of the Jini technology. Specifically the book presents and builds on the
concept of a "virtual development center" It utilizes the concepts of distributed
development, open-source, virtual corporations, and the borderless world as a
backdrop to developing products and services using Jini and JavaSpaces technologies.
Table of Content
Table of Content ........................................................................................................ii
About the Author .......................................................................................................v
Acknowledgments......................................................................................................v
Tell Us What You Think! ..........................................................................................v
Introduction...............................................................................................................vi
What's Ahead?....................................................................................................vi
Part I: The Jini Architecture.......................................................................................1
Chapter 1. Introduction to Jini ...................................................................................2
The Jini Evolution ................................................................................................2
Goals of the Technology ....................................................................................2
What Are Jini Services? .....................................................................................3
How Does Jini Solve Real-World Problems? .................................................5
The Evolving View of Services..........................................................................8
The Tipping Point ..............................................................................................10
Chapter 2. Jini in the Age of Services......................................................................12
The Emerging Networked Application ...........................................................12
Understanding the Layers of Communication ..............................................13
The New Protocols............................................................................................17
Existing Protocols in the Middleware Maze ..................................................26
Summary ............................................................................................................33
Chapter 3. Supporting Technologies........................................................................34
The HTTP Server ..............................................................................................34
Remote Method Invocation..............................................................................37
Summary ............................................................................................................58
Chapter 4. Building on Jini Foundation Concepts ...................................................60
Foundational Services ......................................................................................60
Summary ............................................................................................................90
Part II: Designing Applications Using the Jini Framework.....................................91
Chapter 5. JavaSpaces Service.................................................................................92
The JavaSpaces Service .................................................................................92
The Architecture of JavaSpaces.....................................................................92
Entries Revisited................................................................................................95
The Simple API..................................................................................................97
Distributed Data Structures ...........................................................................100
Application Basics ...........................................................................................103
Deploying JavaSpaces...................................................................................104
A Sample Application—Building the MessagePad ...................................107
Summary ..........................................................................................................123
Chapter 6. Transaction Service ..............................................................................124
Distributed Transactions ................................................................................124
The Jini Transaction Framework ..................................................................127
The Mahalo Implementation..........................................................................131
JavaSpaces Transaction Example ...............................................................132
Transactional Shell Example.........................................................................136
Summary ..........................................................................................................146
Chapter 7. The Helper Services .............................................................................147
ii
Overview of Helper Services .........................................................................147
The Disconnected Client or Service Entity..................................................150
The LDS Interface Explained ........................................................................150
The LRS Interface Explained ........................................................................155
The EMS Interface Explained .......................................................................159
Summary ..........................................................................................................172
Chapter 8. Service Administration.........................................................................173
Service Administration Overview..................................................................173
Client Administration.......................................................................................173
The Administrative Interfaces........................................................................176
Building on Your Service Framework...........................................................180
Client-Side Implementation ...........................................................................192
Summary ..........................................................................................................195
Chapter 9. Security in Jini......................................................................................196
Defining Security Requirements ...................................................................196
Jini-Specific Requirements ............................................................................197
Java Security....................................................................................................197
The Java Virtual Machine ..............................................................................198
Configuring Basic Security with Policy Files ...............................................201
Additional Security Considerations and Alternative Techniques .............205
In Need of Evolution .......................................................................................220
Summary ..........................................................................................................221
Part III: Developing Applications..........................................................................223
Chapter 10. GroupWare .........................................................................................224
Defining the JWorkPlace................................................................................224
The Role of XML in Workplace Definition ...................................................225
The Supporting Services: reggie, outrigger, and mahalo .........................226
Client-Side Service Discovery.......................................................................234
Introducing the ServiceUI Project .................................................................240
Summary ..........................................................................................................249
Chapter 11. Workflow ...........................................................................................250
What Is Workflow? ..........................................................................................250
A Collaborative Workflow Framework Example .........................................253
The Supporting Services................................................................................255
Change in State as a Flow-of-Objects .........................................................255
The Event Mailbox Service as a Remote Event Notification Platform....260
Integrating Workers with JavaSpaces and the Event Mailbox Service ..266
Content Management Workflow ...................................................................282
Summary ..........................................................................................................286
Chapter 12. Agents.................................................................................................287
Agents of Collaboration ..................................................................................287
Simple Agent Frameworks.............................................................................288
An Example Process Control Agent.............................................................290
Future Jini Agents ...........................................................................................299
Summary ..........................................................................................................300
Chapter 13. Jini as a Web Service .........................................................................302
Business-to-Business E-commerce .............................................................302
What Are Web Services?...............................................................................302
Simple Object Access Protocol (SOAP) as a Transport Technology .....303
Jini in the B2B Context ...................................................................................312
iii
SOAP-to-Jini Integration ................................................................................313
Summary—The Road to (Dis)-Integration...................................................318
Chapter 14. Pervasive Computing and Mobile Devices ........................................320
Jini Environment and Assumptions ..............................................................321
Can Jini Scale Down? ....................................................................................322
Will Devices Scale Up? ..................................................................................331
The Network Reality—Pervasive Computing .............................................332
Summary ..........................................................................................................332
Part IV: Appendixes...............................................................................................334
Appendix A. Setting Up Your Environment..........................................................335
What You Will Need........................................................................................335
Gotchas and Common Pitfalls.......................................................................336
Appendix B. Tools .................................................................................................339
Start-up Scripts................................................................................................339
Building Jini ......................................................................................................340
Service Browsing and Administration ..........................................................341
Summary ..........................................................................................................343
Appendix C. Links, Community, Resources .........................................................344
Links ..................................................................................................................344
Specifications...................................................................................................344
Community .......................................................................................................344
Resources ........................................................................................................345
iv
About the Author
Robert Flenner is an active author, software developer, and independent contractor. His articles
on e-commerce, Java development, and Jini have been featured on many popular Web sites. He is
actively involved in business-to-business technologies, peer-to-peer technologies, and GroupWare
applications.
Robert has extensive experience consulting with global companies on both strategic and tactical
system development and deployment. His technical consulting experience includes Web, CORBA,
distributed object modeling, workflow, and high-availability transaction processing.
He is currently promoting and developing a framework for distributed development and
collaboration using Jini and JavaSpaces technologies at www.jworkplace.org.
Robert can be reached [email protected].
Acknowledgments
I am very grateful to the many people who helped with this book and provided valuable support.
I would like to thank Michael Stephens at Sams Publishing for giving me the opportunity to write
this book.
All the folks at Sams, especially Christy Franklin, Seth Kerney, Christina Smith, and technical
reviewer Piroz Mosheni, that assisted with the production, design, and integrity of the book.
Without their effort, this book would not be possible.
Additionally my thanks to the Jini community, who daily provide insight and direction, and are
bound together by this technology.
And of course my family and especially my wife Jane for her encouragement and never ending
support.
Tell Us What You Think!
As the reader of this book, you are our most important critic and commentator. We value your
opinion and want to know what we're doing right, what we could do better, what areas you'd like
to see us publish in, and any other words of wisdom you're willing to pass our way.
As an Executive Editor for Sams Publishing, I welcome your comments. You can fax, e-mail, or
write me directly to let me know what you did or didn't like about this book—as well as what we
can do to make our books stronger.
Please note that I cannot help you with technical problems related to the topic of this book, and
that due to the high volume of mail I receive, I might not be able to reply to every message.
v
When you write, please be sure to include this book's title and author's name as well as your name
and phone or fax number. I will carefully review your comments and share them with the author
and editors who worked on the book.
Fax:
E-mail:
Mail:
317-581-4770
[email protected]
Michael Stephens
Executive Editor
Sams Publishing
201 West 103rd Street
Indianapolis, IN 46290 USA
Introduction
This book is about developing Jini and JavaSpaces applications in the midst of a technology
revolution. It is about emerging technologies that are changing the way we build and think about
systems. The intent is to provide a practical approach to using and developing Jini services by
working through examples.
Although you will cover the framework and the concepts underlying Jini, the primary goal is to
provide practical guidance for developing applications and services in a Jini network. In addition,
a wide range of application areas to discover and extend Jini capabilities are covered.
This book is for Java developers, architects, and managers of distributed applications who require
practical knowledge of Jini services, and the role those services play in building network-centric
applications.
This book also highlights three major trends shaping the new networked applications—Web
services, peer-to-peer (P2P) systems, and the Semantic Web.
Where and how does Jini fit into these efforts? This book will attempt to answer these questions. It
will also demonstrate how Jini is well positioned for the technology changes unfolding on the
Internet and the changes driving the business-to-business and the emerging peer-to-peer markets.
What's Ahead?
Part I: The Jini Architecture
Chapter 1—Introduction to Jini
The notion of a service is fundamental to Jini. This chapter provides an overview of Jini services.
It also compares and contrasts Jini to competing technologies and trends in distributed application
development.
Chapter 2—Jini in the Age of Services
What differentiates a Jini network from other networks? This chapter defines a Jini network. The
objective is to clarify many misconceptions about the proper positioning of Jini in the Open
Systems Interconnection (OSI) model.
vi
Chapter 3—Supporting Technologies
This chapter presents an overview of remote method invocation (RMI). It builds a sample nonactivatable and activatable service to prepare for the introduction of Jini concepts. The intent is to
provide you with enough background to get started in setting up the Jini environment and an
understanding as to how the supporting services fit.
Chapter 4—Building on Jini Foundation Concepts
This chapter is critical to the overall comprehension of the Jini framework. It will provide you
with sufficient detail to grasp key Jini concepts. Multicast and unicast discovery is discussed in
detail, as well as the role of the lookup service. You will evolve the RMI service built in Chapter 3
to your first Jini service.
Part II: Designing Applications Using the Jini Framework
Chapter 5—JavaSpaces Service
This chapter provides an overview of the JavaSpaces service. It extends the notion of entries
defining service attributes to entries defining models of communication and control in JavaSpaces,
such as messaging systems and workflow control systems.
Chapter 6—Transaction Service
This chapter explains distributed transactions and how they are implemented in the Jini model.
This chapter provides an overview of the Jini Transaction service.
Chapter 7—The Helper Services
This chapter provides an overview of the Jini 1.1 helper services and how they can be used to
complement core and user-defined services. This includes the lookup discovery service, the lease
renewal service, and the event mailbox service.
Chapter 8—Service Administration
How are Jini services administered? How can you reduce the human requirements on service
administration? This chapter answers these questions and introduces the service administration
interfaces. It explains the management of groups, locators, and entries in service administration. In
addition, you will define a Jini service to provide administrative functionality.
Chapter 9—Security in Jini
How is security implemented in Jini networks? This chapter explains the current state of security
in Jini technology. It highlights the dependencies on the Java 2 platform, RMI, and the Java
language. In addition, common security mechanisms and standards are evaluated for distributed
Jini services.
Part III: Developing Applications
Chapter 10—GroupWare
This chapter provides practical applications and examples of the Jini and JavaSpaces concepts
introduced in the prior two parts. You will develop a sample GroupWare application that includes
peer-to-peer content sharing, visual UI interface techniques, and online chat service. This example
vii
is based on JWorkPlace, the author's original creation. Visit www.jworkplace.org for more
information.
Chapter 11—Workflow
This chapter demonstrates the power of Jini and JavaSpaces in controlling distributed workflow
processes. It presents the author's concept called STAR. This acronym breaks down as (S)pace,
(T)ime, (A)ctivity, and (R)esource entries as they define the core model in distributed workflow
processing. An example workflow application is presented that uses the Jini Event Mailbox
service and JavaSpaces.
Chapter 12—Agents
This chapter provides an overview of software agents and the mapping of agent requirements to
the Jini services. Demonstration of simple agents of collaboration in a development scenario is
explained. Exercises in Chapters 10 and 11 are built upon to automate communication between the
development community. The example uses the Jini Lease Renewal Service for managing
resources and providing time event notification.
Chapter 13—Jini as a Web Service
This chapter presents Jini in the context of the current trend towards XML-defined Web services.
It provides an overview of the Web Service Definition Language (WSDL), Universal Description
and Discovery Interface (UDDI), and Simple Object Access Protocol (SOAP). The intent is to
demonstrate how Jini can play a part in service discovery and B2B marketplace evolution.
Chapter 14—Pervasive Computing and Mobile Devices
The final chapter presents Jini in the emerging mobile device space. It includes an overview of the
Java 2 Micro Edition (J2ME) platform, which includes Java support for mobile devices. The
chapter also includes an overview of the Surrogate architecture, which has been developed to
support resource constrained devices in a Jini network. Can Jini scale down? Will devices scale up?
This chapter asks and answers these questions.
Appendix A—Setting Up Your Environment
Appendix A provides the information necessary to get your Jini environment running. It includes
links to important Jini distribution information, system requirements, and a troubleshooting
section of "Gotchas and Common Failures."
Appendix B—Tools
Appendix B provides an overview of important Jini tools for new developers. It includes service
startup scripts, building the Jini distribution using the Build Tool, and the Lookup Browser for
service administration.
Appendix C—Links, Community, Resources
Appendix C provides links to valuable Jini resources available on the Web.
viii
Part I: The Jini Architecture
IN THIS PART
1 Introduction to Jini
2 Jini in the Age of Services
3 Supporting Technologies
4 Building on Jini Foundation Concepts
1
Chapter 1. Introduction to Jini
IN THIS CHAPTER
•
•
•
•
•
•
The Jini Evolution
Goals of the Technology
What Are Jini Services?
How Does Jini Solve Real-World Problems?
The Evolving View of Services
The Tipping Point
The Jini Evolution
There are two important trends in the Jini evolution: The focus on evolving solutions to promote a
more effective global network using the Internet, and the common concepts and terminology that
are emerging from those solutions. It is my belief—and one shared by many others—that
networks and networked services are being driven toward the solutions supported by Jini.
Jini is one of the current catalysts for change in the era of the networked application. Jini provides
a solution for defining, deploying, and administering services on a network. The following
definition comes from the official Jini architecture specification, located on the Sun Web site
(http://www.sun.com/jini/specs):
A Jini system is a distributed system based on the idea of federating groups of users and the
resources required by those users. The focus of the system is to make the network a more dynamic
entity that better reflects the dynamic nature of the workgroup by enabling the ability to add and
delete services flexibly.
Jini network technology provides a network-centric view of services and service delivery.
Services have a broad definition; from applications to mobile appliances and anything in between
that is on the network. Jini is based on the Java platform, and has both the advantages and
disadvantages inherent in that platform.
Jini has the built-in capability of understanding Java types. Services and interfaces are defined
using Java types. Types are richer than XML in being able to define semantics between machines
to exchange information. As Jini is based on Java, which is an object-oriented language, it
supports polymorphism. Polymorphism permits us to support objects of different types in
consistent ways; it helps us deal with technological evolution.
Of course, Java is more difficult to learn than XML, and Java has the disadvantage of not being
the smallest or fastest engine on the block. In addition, the current Jini release relies heavily on the
RMI (Remote Method Invocation) protocol, although this is not a requirement.
Goals of the Technology
The overall goal of Jini is to "…turn the network into a flexible, easily administered tool on which
resources can be found by human and computational clients." Jini attempts to minimize the
amount of administration required to operate and manage a network of services. It is recognized
2
that the proliferation of computers and devices that contain computers is outpacing current
administration capabilities. Jim Waldo of Sun Microsystems made the point that computers will
soon outnumber humans on the planet. How can we continue to manage our networks and millions
of devices connected to the network—cable or wireless—with current practices?
Today, system and network administration still requires a significant amount of human
intervention and monitoring. Jini services require less configuration and administration because
Jini provides a framework for knowledgeable services. Knowledgeable services are aware of the
environment in which they inhabit. Knowledgeable services actively participate in coping with
changes to that environment.
Jini provides a solution to network and system instability. For years I dealt with technologies that
promoted location and network transparency. The idea was that client applications should not care
whether the process invoked is local or remote. Using these technologies, we tried to mask process
remoteness from the client. This would enable the same programming model to function in either
scenario, and remote processes could move on the network without requiring changes to client
code.
However, process or service location is important! Distributed services have the potential to
behave differently than local services, and therefore the mask is superficial. Any sophisticated
application ultimately has to deal with issues such as latency, and partial and permanent failures in
distributed systems and networks. So, while the invocation can be made consistent, the results
cannot.
Jini was designed to recognize that there is a difference between the remote and local model that
cannot be hidden. In fact, the model ensures that services are built to accommodate failures and
instability in systems and networks.
What Are Jini Services?
The notion of a service is fundamental to Jini. The Jini Specification defines a service as
"…something that can be used by a person, a program, or another service." Jini services are
usually manifested as Java objects that expose an interface that conforms to the Jini specification.
The type of service determines the interfaces that make up that service. Jini services advertise their
operations by registering an object with a Jini-compliant lookup service. Service registration is at
the core of building the Jini network community.
The Lookup Service
Every Jini community must have at least one registry or lookup service available. The lookup
service (LUS) to Jini can be compared to:
•
•
•
•
The registry in RMI (Remote Method Invocation)
The Name Server in DNS (Domain Name Systems)
The COS Naming Service in CORBA
The UDDI in Web services
Each of these systems provides a mechanism for resolving a name to an information processing
location. Each system takes a different approach and extends the functionality in different ways.
For instance, RMI maps a name (String) to an object. DNS maps hostnames to IP addresses,
and UDDI provides a directory to register a Web service.
3
The LUS enables service-using entities to register and find services that meet specific criteria. The
LUS provides functions similar to a yellow page service. To Jini, the LUS is just another service
on the network, albeit an important one. The LUS is what in effect bootstraps the Jini network.
Services register with the LUS, and applications find services of interest using the LUS. After a
service is found, an application can invoke the methods that the service exposes through its public
interface.
The LUS addresses the requirements that broadly refer to the notion of discovery. This notion is
refined in this book by examining the degree of dynamic discovery required in an application and
the alternative approaches.
The LUS is defined by the Sun Lookup Service Specification. Sun Microsystems has developed
an implementation of the LUS called reggie.
The Lookup Discovery Service
The lookup discovery service (LDS) provides support for disconnected clients. It acts as a proxy
for the Jini client to discover changes in the services composing the Jini network. For instance,
services can become active and join the Jini community at any time. In addition, they can become
inactive or unreachable. One approach sometimes seen to solve dynamic network changes is
through broadcasting and replication. With the LDS, a proxy can alert services of changes by
activating a service based on a type of discovery event. The service registers interest with the LDS
for notification. This reduces the resource load on systems, and eliminates the need for clients and
services to remain active.
The LDS is defined by the Sun Lookup Discovery Service Specification. Sun has developed an
implementation of the LDS called fiddler.
The Lease Renewal Service
One of the concepts introduced in Jini is resource leasing, or time-based resource allocation.
Leasing of resources has direct implications on the programming model used and the services that
must be implemented to support a Jini network. Leasing implies that nothing is permanent in the
Jini system. You must renew leases and renew interest in resources in order for them to remain
active and available. Otherwise, they will be purged from the network by their removal from the
LUS. Imagine how much friendlier the Web would be if bad or invalid links were just magically
purged or disappeared!
There is a lease renewal service—defined as a helper service in the Jini framework—that renews
interest in resources for you. This is another service that enables disconnected clients and
minimizes the amount of work required by developers that use the Jini model. You delegate the
responsibility to renew leases to the lease renewal service. So, your service can then disconnect/go
idle and not have to worry about losing leases.
The lease renewal service (LRS) is defined by the Sun Lease Renewal Service Specification. Sun
has developed an implementation of the LRS called norm.
The Event Mailbox Service
Notification of changes in the state of network services is enabled by remote events. Events have
long been understood as a technique for state change notification. The event model provides the
capability to decouple event observers from event sources. This promotes consistency in change
notification and reusability. The Jini event interface is simple and designed to enable event chains
or filters to grow dynamically as events dictate.
4
The Jini framework provides an event mailbox service that provides event "store-and-forward"
functionality. This enables a using entity to disconnect from the network and have the service
queue events until those events are requested by the using entity.
The event mailbox service (EMS) is defined by the Sun Event Mailbox Service Specification. Sun
has developed an implementation of the EMS called mercury.
The Transaction Service
Transactions are at the core of legacy and e-commerce systems. Jini defines a lightweight
transaction interface that supports a two-phase commit protocol. The intent of the transaction
interface is that services will determine the degree of transactional integrity required, depending
on the application context. Unlike typical database transactional systems or transaction monitors,
Jini does not try to guess the degree of transactional integrity required. It does, however, provide
common transaction semantics that can be assumed by using entities. In other words, a
tremendous amount of flexibility is left to the implementation.
The transaction service is defined by the Sun Transaction Service Specification. Sun has
developed an implementation of the transaction service called mahalo.
The Persistence Service
JavaSpaces is an example of a Jini service that runs on the Jini network and is closely tied to the
Jini architecture. JavaSpaces was heavily influenced by the concept of a tuple space, which was
first described in 1982 in a programming language called Linda. The basic idea is that you can
have many active programs distributed over physically dispersed machines, unaware of each
other's existence, and yet the programs are still able to communicate. They communicate to each
other by updating shared memory. Programs read, write, and take entries from this shared
workspace.
Peer-to-peer systems have already demonstrated the need for a distributed network of loosely
coupled collaborating processes. These processes require task management and coordination.
JavaSpaces provides a lightweight implementation of persistence that can aid in implementing a
collective memory.
The JavaSpaces service is defined by the Sun JavaSpaces Service Specification. Sun has
developed an implementation of the persistence service called outrigger.
Links to all of these service specifications can be found in Appendix C.
The Killer Service
Of course, the killer Jini service is the one waiting for you to develop! The services just mentioned
form the building blocks. This book demonstrates the usage of these foundational services in
application-specific scenarios. The intention is to arm you with enough knowledge to start down
the path to building the next generation of networked applications.
How Does Jini Solve Real-World Problems?
We have taken a broad look at the systems and changes occurring in networked-applications. But,
how does Jini solve real-world problems?
5
The Benefits of Distribution and Parallelism
Jini is based on the premise that services are distributed across a network. The benefits of
distribution are well-known. We generally distribute processing when we need to scale our
systems to support increased demand for resources. We distribute for geographic reasons, to move
resources and processes closer to their access point. We distribute to provide better fault resistance
and network resilience. We distribute resources to enable sharing of resources and promote
collaboration.
Of course, with distribution comes a number of significant challenges. The management of the
network is much more difficult. One of the advantages of centralization is central administration
and monitoring. Knowing where resources are and how they are behaving is a tremendous
advantage. In a distributed environment, failures are not always detected immediately. Worse yet,
partial failures allow for results and side effects that networks and applications are not prepared to
deal with. Response time and latency issues introduced as a result of remote communication can
be unpredictable. The network can have good days and bad days. The bad days can damage the
reputation and usability of a product in the eyes of the user community.
Application-to-application interaction might become unstable as error paths and timeouts get
triggered excessively. Synchronization can often require excessive strain on bandwidth.
Any solution that is based on distribution should be able to eliminate or mitigate these issues. Jini
has evolved more than most in this area of management.
Parallelism is not new to computing. In fact, much of what we do in computing is done in parallel.
Multiprocessor machines and operating systems rely on the ability to execute tasks in parallel.
Threads of control enable us to partition a process into separate tasks. However, to date,
parallelism has not been the norm in application development. While we design applications to be
multithreaded, this generally has involved controlling different tasks required of a process, such as
reading from a slow device, or waiting for a network response. We have not defined many
applications that run the same tasks in parallel, such as searching a large database, or filtering
large amounts of information concurrently.
Parallelism can provide us with a divide-and-conquer approach to many repetitive tasks. The
SETI@Home project demonstrated that personal computers could be harnessed together across the
Internet to provide extraordinary computing power. The Search for Extraterrestrial Intelligence
project examines radio signals received from outer space in an attempt to detect intelligent life. It
takes a vast amount of computing power to analyze the amount of data that is captured and the
computations involved. People volunteered for the project by downloading a screensaver from the
SETI Web site. The screensaver was capable of requesting work units that were designed to
segment the mass amount of radio signals received. In the first week after the project launched,
more than 200,000 people downloaded and ran the software. This number grew to more than
2,400,000 by the end of 2000. The processing power available as a result of this network outpaced
the ASCI White that was developed by IBM and is the fastest supercomputer built to date.
Jini and JavaSpaces are designed to meet this growing trend in divide-and-conquer strategies.
Coping with the Inherent Failure of Networks and Hardware
Jini was built under the assumption that services are distributed over a network, and that the
network is unreliable. Therefore, to mitigate the issue of reliability, Jini introduced the concept of
leasing. Leasing ensures that failing services get recognized and removed from the supporting Jini
community. For example, to be recognized on the Jini network, you must register with the LUS.
To remain active in the LUS, you must renew a lease. This renewal process ensures that garbage
6
does not accumulate in key components, and it does this without requiring human intervention. It's
part of the overall framework that makes Jini work.
Exploiting Interface-Based Design
Object-oriented development has been proclaiming the virtues of designing with interfaces as
opposed to concrete implementations for more than a decade. Jini relies heavily on this concept.
An interface provides a standard protocol for interacting with objects that implement the interface.
Interfaces are key to enabling alternative implementations and technology evolution. They permit
the semantics of the interaction to evolve separately from the implementation, which gives
providers tremendous flexibility. For instance, provider implementations might optimize different
requirements, such as scalability, footprint size, throughput, and so on. Users can select the service
that best fits their needs and not change the client interaction.
Of course, this is not new to distributed development and object technology in general. The
Common Object Request Broker Architecture (CORBA) developed by the Object Management
Group (OMG) defines interoperability based on interface definitions. CORBA also provides
separation of interface definition from implementation. In CORBA and many other technologies,
Interface Definition Language (IDL) is used to provide this property. However, there are
differences enabled through RMI, such as dynamic class loading that permits subclasses to be
resolved by remote objects even though that subclass is not defined by the original interface. A
remote object might not even have the definition of a subclass available, but can retrieve the class
definition by requesting and marshalling the object across the network via RMI. This concept is
not supported natively in any of the other technologies.
Promoting Service Reuse
Promoting service reuse will be the key to the success of service-based architectures. In other
words, if I have to re-invent these underlying services for each application, then the benefit
becomes questionable. Service reuse will not be an easy task to implement. Although it is easy to
imagine a network of services, cooperation and collaboration of services internally and externally
to an organization can be an elusive goal.
To promote reuse, Jini provides:
•
•
•
•
Object-oriented types and interface sub-typing
A standard mechanism for publishing and advertising services
Improved quality of service through self-healing networks
Reduced time of development by providing core services out of the box
The Veracity in the Technology
So why hasn't Jini taken off and been adopted as quickly as some had projected in 1999? Ask any
two people and you will get two different answers. My opinion can be summarized as follows:
After the initial introduction, Jini became labeled as the device technology. It got promoted as the
technology that was going to enable devices to flourish on the Internet. Your refrigerator could
actually dial-up your refrigerator repairman without you knowing it! Every appliance in your
house would begin to appear on your home network. Software agents and devices would be
interacting to bring you the cartoon world of George Jetson. Well, it didn't happen in 1999, and it
hasn't happened in 2001. My refrigerator has not made a single call, has yours?
Unfortunately, the appliances most likely to use this technology initially were cell phones and
Palm Pilots, neither of which is known for an abundance of processing capacity. These devices
tend to be resource-constrained. Although Jini was designed with a relatively small footprint, it
7
was in excess of the easy reach for most devices at the time. So, rapid adoption by the device
market did not occur.
In addition, Jini deals with complex networking issues. The learning curve to Jini cannot be
underestimated. Many developers new to Jini find it difficult to get started with the technology.
Jini relies on technologies like Java and RMI—at least the initial implementation—in addition to
network technologies. Jini builds on these technologies, thus making it more difficult for the
newcomer. Of course, this is also the strength of the framework, because it does not try to invent
something that already exists.
The general lack of Jini tools has not helped the learning process. In contrast, the JXTA project
has released the JXTA Shell as an application that enables users to interact with the JXTA
platform through a command-line interpreter. It is an example application to jump-start the
learning and discovery process in JXTA.
Jini has not released anything comparable to the JXTA Shell. A service start-up program that was
"buggy" was all that was delivered.
In contrast to the difficulty in learning Jini is the ease with which XML can be adopted. Couple
this with the Web services, and you have yet another impediment to adoption. However, I will
contend that XML has tremendous benefits if applied properly—such as document definition and
technical configurations—but will be stretched to deal with evolving network applications,
including changing behavior and versioning.
Successful adoption of a technology requires many things to come together in the right way. These
things could include the following:
•
•
•
•
•
The integrity of the product
The demand for the functionality
The maturity of the market
The availability of skilled resources
The cost to a corporation and individual
Competing Technologies
Of course, since the introduction of Jini, many competing technologies have surfaced that are
quickly carving out niches in this global network of services. In some respects, getting recognized
in the crowd will become more difficult. We will not be able to look at any solution in a vacuum.
We have the opportunity to avoid the mistakes of the past—developing narrow solutions—by
developing interoperable applications. However, one size will not fit all. This choice will require
that we look at more than one solution, and develop and evolve our level of understanding of peer
patterns, service patterns, and the semantics of the relationships between them.
The Evolving View of Services
Almost everyone is talking about services of interacting with services. As mentioned previously,
Jini is based on services that are available on a network.
The Current "Tug-of-War"—Services Versus Devices
Jini blurs the distinction between hardware and software. This might have contributed to the initial
"technology for intelligent devices" mantra that was echoed so strongly in most publications
8
defining Jini. But at the core, Jini is about services. It is about delivering services regardless of the
platform. Whether the code is burnt into a device or deployed on a farm of servers is not the issue.
The intent is that services are not limited to traditional platforms. The Jini design recognizes the
emergence of devices as the next logical platform for executing networked-application code.
The Complexity of Cooperation and Collaboration
Service-based architectures that use dynamic discovery will be difficult to build. Despite the
claims that XML-based protocols will usher in a new level of standard definitions, XML is simply
a markup language. The difficult part is still gaining consensus on the usage and conformance to
the specifications. XML has evolved with many standards groups, industry groups, and
independent organizations defining metadata definitions that are not compatible. It is not clear
how many competing definitions will ultimately survive.
As of this writing, there is a tremendous amount of hype over Web services, although major gaps
still exist in the definition. For instance, how is service composition defined, and how is the
coordination of message interaction choreographed?
Even Jini does not adequately define service composition. However, projects like RIO
(http://www.developer.jini.org/exchange/projects/rio/) and
Abaco(http://www.developer.jini.org/exchange/projects/abaco/) are presenting solutions using
container-based models. These containers provide the services that control quality of service,
operations, administration, and management parameters.
Comparing Jini to Other Solutions
How is Jini different from other solutions?
Dynamic Code Movement
A primary difference between Jini and other solutions is its capability to move code around the
network. Because it uses RMI and object serialization, it is able to download code on demand.
There is no human intervention required in the process. This has some distinct advantages over
other systems being defined. The client of a service does not need the software installed prior to
running the application. As long as the client can attain the interface to the service, it can execute
the code. Software agent technology, especially mobile agents, can benefit from the framework
Jini already has established.
Dynamic Construction and Formation of Groups and Communities
Dynamic discovery enables peers and services to find each other. It allows for the quick formation
of groups and communities sharing a common interest. However, speed of assembly must be
tempered with network integrity.
Jini uses a combination of unicast and multicast protocols to dynamically discover services on the
network. There is the notion of a "well-behaved service" that implements these protocols in a
specific manner. The intent is to ensure broadcast storms do not occur, and that there is a higher
level of resiliency in the network.
Resource conservation will be required in any system that needs to scale dynamically. Whether it
is enforced explicitly in the API and/or implicitly through "rules of engagement" remains
unknown. Applications that use broadcast techniques to discover peers can contribute to serious
network congestion when traffic does not get distributed appropriately. Just how dynamic service
discovery will be in the Web services architecture has not been determined.
9
There is no clear winner is this space. However, one of the key components of the Jini framework
is dynamic service discovery and the controlled formation of communities.
Self-Healing Networks
Jini introduces another concept to minimize disruption; that of leasing resources. It is fundamental
to the Jini model. It ensures that resources that are no longer available eventually get removed
from the network. This is not done through human intervention, but rather through system
grooming. This has been compared to a "survival of the fittest" philosophy. Services that don't
perform well will eventually lose their lease in the Jini network and be pruned from the system.
Every resource must have a management process (leasing) that periodically checks to see if
everything is OK, and to ensure the resource is still required by the system or user.
Leasing is one of the most important features, providing the robustness of the Jini infrastructure.
Leasing permits the LUS to delete services that have failed to renew their lease. If you provide a
service, or if you are using a service, you must periodically renew your lease. The renewal period
is dependent on the application. You request a time-based renewal period on your lease, and the
service provider determines the actual lease commitment.
Today when you use the Web, often you are hit with bad or invalid URL references, or sites that
are slow to respond. Business-to-Business (B2B) e-commerce is being built on this same
infrastructure and architecture. System and network instability will be an issue. B2B e-commerce
will require the self-healing properties of a Jini network. In addition, Jini supports the core
requirements of B2B e-commerce—distributed transactions.
Richer Type-Based and Polymorphic Service Lookups
Jini is Java-based. Interfaces define the services and the protocols in the Jini network. Jini does not
support the notion of XML being fundamental to the process of defining and accessing services on
the network. In terms of the other solutions, Jini appears to be the maverick.
Jini relies on the richness of type semantics. There is also the belief that these type definitions and
the ease with which they support sub-typing (inheritance) resembles the way we build systems.
Systems can evolve as the technology evolves. The other approaches rely on XML definitions or
APIs that limit the systems' adaptability to change.
This introduction has tried to convince you that many of the requirements of the new wave of
distributed services are now aligned to push Jini into the forefront of the emerging networkedapplication.
The Tipping Point
In the Malcolm Gladwell book The Tipping Point, the author describes social, economic, and
technological changes that happen very quickly and sometimes from small events. He compares
the spread of these changes to epidemics, and cites numerous examples.
The tipping point for Hush Puppies—a company that makes brushed-suede shoes—happened in
the mid-nineties. The brand was facing extinction, with sales totals reaching only 30,000 pairs per
year. The company responsible for making the shoe was contemplating phasing out the line.
Then something unusual started to happen. The shoe became popular in the trendy Soho district of
New York. Young males were buying up the shoe in resale stores, and it was becoming hip in the
clubs and bars of downtown Manhattan. The story goes on to recount the rapid increase in
10
popularity, all without any promotional activity. The increase in popularity was attributed strictly
to word of mouth and the "right" people wearing the shoes and spreading the look. In 1995 the
company sold 430,000 pairs of the shoes, and doubled that in the next year. The year 1995 was the
tipping point for Hush Puppies.
Similar stories are told throughout the book of changes that start small but quickly spread like
epidemics; changes that many times go against our intuition and experience. Gladwell concludes
that starting epidemics requires concentrating resources on a few key areas and a few key types of
individuals, and testing your own intuition.
I believe there are changes happening in networks that are going to cause technologies—some
new, some not so new—to soon hit the tipping point. Jini has the potential to be one of them. If
not Jini, some technology that embodies Jini's characteristics will soon find itself in that position.
Understanding the concepts and techniques employed by Jini technology will prepare you for the
inevitable tipping point in the networked application.
11
Chapter 2. Jini in the Age of Services
IN THIS CHAPTER
•
•
•
•
The Emerging Networked Application
Understanding the Layers of Communication
The New Protocols
Existing Protocols in the Middleware Maze
What differentiates a Jini network from other networks? This chapter will define a Jini network.
The objective is to clarify many misconceptions about the proper positioning of Jini in the Open
Systems Interconnection (OSI) model, and compare and contrast a Jini network to the emerging
Web services, peer-to-peer systems, and the Semantic Web.
The Emerging Networked Application
In Chapter 1, you learned about the rise of the networked application. It is not new. It has been
around since networks themselves. It involves application-to-application (A2A) communication
over a shared private or public network, as illustrated in Figure 2.1.
Figure 2.1. Application-to-Application (A2A) communication.
Protocols and Services
The fundamental requirement supporting this model is that the applications agree on the
information exchange and the service that is provided. This information exchange is referred to as
the protocol. A protocol is a set of rules and accepted conventions for communication. It defines
the packet (exchange) format and data structure to use for service requests; the service being the
agreement or the expected action as a result of the exchange. Protocols are defined at different
levels of abstraction, from low-level transmission protocols to high-level application semantics.
Any non-trivial application being defined today must consider the implications of emerging
protocols and services.
12
In addition, applications are required to be more adaptive to the systems they inhabit and the
networks they roam. As a result, development has become more complex. Programming
languages, toolkits, and integrated development environments have had trouble keeping pace with
the rate of change in applications. What once was considered impossible over networks has
become commonplace.
The explosive growth of network technologies is promoting new business opportunities and
business partnerships. Globalization of the business economy will only fuel demand for networked
applications. In addition, network devices are gaining popularity with both consumers and
business market segments. The proliferation of wireless technologies is forcing us to rethink our
assumptions on application and network protocol integration.
The applications being developed today are more closely tied to the network than ever before. Of
course, the Internet and the Web have been the most recent catalysts for this change. The
proliferation of applications on the Internet is the result of the establishment of standard
protocols—namely HTTP over TCP/IP.
With the standardization and ubiquity of TCP/IP networks, higher-layer protocols have become
the new battleground for application interoperability. The new breed of protocols is battling for
market and mind share to influence and direct the development of post-millennium applications.
Let's review networking protocols and the services that have evolved to support the global-local
network. Then the new protocol drivers will be put into perspective.
Understanding the Layers of Communication
Protocol layering is a common technique for simplifying network design by defining functional
layers and assigning protocols to perform each layer's task. For example, it is common to separate
the functions of data delivery and connection management into separate layers, and therefore
separate protocols.
Protocol layering produces simple protocols, each with a few well-defined tasks. These protocols
can then be assembled into a useful whole. Individual protocols can also be removed or replaced
as needed for particular applications.
A layered approach is considered better practice than implementing a large "brick" of code that is
difficult to understand. Layered architectures promote reuse, exchangeability, incremental
development, and offer the advantage of localizing changes to specific layers. As the network
evolves, layers can be replaced or exchanged without reengineering the entire communication
architecture.
Open System Interconnection (OSI) Model
The International Standardization Organization (ISO) defined the OSI 7-layer model, illustrated in
Figure 2.2, to describe the protocols and services required for A2A communication. The origins of
the work can be traced back to 1977. To this day it is used as the reference model to discuss and
compare application communication over distributed systems.
Figure 2.2. The OSI reference model.
13
The model provides the following layer definitions.
Physical Layer
Layer 1 is the physical layer of the OSI model. It defines the physical and electrical characteristics
of the network. The NIC cards in your PC and the interfaces on your routers all run at this layer.
Data-Link Layer
Layer 2 is known as the data-link layer. It defines the access strategy for sharing the physical
medium. Protocols, such as PPP, SLIP, and HDLC reside here. On an Ethernet, this is where the
access to each NIC takes place. Devices that depend on this layer include bridges and switches.
Network Layer
Layer 3 is the network layer, providing a means to establish, maintain, and terminate network
connections. The IP protocol resides at this layer as do some routing protocols. Routers in the
network are operating at this layer.
Transport Layer
Layer 4 is the transport layer, and it is where TCP and UDP reside. For TCP, this layer relieves the
session layer's burden of ensuring data reliability and integrity. Should a packet fail to arrive—
perhaps because of miswriting—it will be retransmitted to this layer.
Session Layer
14
Layer 5 is the session layer. It provides services for the management and control of data flow
between two computer systems. For example, the session layer enables the insertion of
synchronization points in the flow of application information between computer systems.
Management services enable activities to be started, halted, and abandoned under the direction of
the application. Session management has implications to the load balancing that are possible
across different servers in a server pool. For instance, care must be taken to understand when a
session is taking place not to interfere with it.
Presentation Layer
Layer 6 is the presentation layer. This is where application data is either packed or unpacked,
ready for use by the running application. Protocol conversions, encryption/decryption, and
graphics expansion all take place here. This is the layer that ensures a common representation of
application information is provided while information is in transit between computer systems.
Application Layer
Finally, Layer 7 is the application layer. This is where the end user and end-application protocols,
such as HTTP, telnet, ftp, and mail (pop3 and smtp) are mapped; although OSI purists might only
map ACSE, ROSE, FTAM, and other OSI-compliant protocol data units.
OSI is an important reference model, and TCP/IP has become the de facto standard for the
network and transport layers. The definition above the transport layer, however, tends to be
application-specific. Session, presentation, and application are often collapsed into a single
process-application layer.
The DoD Model
The Department of Defense Four-Layer Model was developed in the 1970s for the DARPA
Internetwork Project that eventually grew into the Internet. The core Internet protocols adhere to
this model, seen in Figure 2.3.
Figure 2.3. The DoD reference model.
15
The four layers in the DoD model are the network access layer, the Internet layer, the host-to-host
layer, and the process layer.
Network Access Layer
The network access layer is responsible for delivering data over the particular hardware media in
use. Different protocols are selected from this layer, depending on the type of physical network.
Internet Layer
The Internet layer is responsible for delivering data across a series of different physical networks
that interconnect a source and destination machine. Routing protocols are most closely associated
with this layer, as is the IP Protocol, which is the Internet's fundamental protocol.
Host-to-Host Layer
The host-to-host layer handles connection rendezvous, flow control, retransmission of lost data,
and other generic data flow management. The TCP and UDP protocols are the dominant Internet
protocols at this layer.
Process Layer
The process layer contains protocols that implement user-level functions, such as mail delivery,
file transfer, and remote login.
You can map common Internet application protocols easily into the DoD model, seen in Figure
2.4.
16
Figure 2.4. Protocol mapping to the DoD model.
What surfaces from the mapping is the variety of protocols that have emerged in the process
application space. In addition, rather than defining similar supporting functions and protocols per
layer, the protocols at the higher layers address specific application needs. For instance, SMTP
provides mail support, while FTP provides file transfer capabilities. Both protocols require
common access to remote systems; however, each protocol manages sessions, presentation, and
application functionality specific to its unique requirements. This lack of factoring out common
functions at higher layers of the OSI model has driven single-application, single-protocol
development.
While HTTP has become the de facto standard communication protocol for the Web, it is not clear
how emerging protocols will leverage or compete with its definition. Many have decided to
simply tunnel through HTTP for transport and define higher-level application-specific protocols
and services to complement it. Streaming audio, instant messaging, remote method invocations,
and even mobile agents are being tunneled through HTTP. Often this decision is based on the
global access and reach HTTP enjoys into organizations and through firewalls.
However, despite the dominance of HTTP, a new battleground for protocols is taking place above
the transport layer.
The New Protocols
This section will focus on the protocols being defined in the higher layers above the transport. We
will outline the services and protocols supporting:
•
•
•
E-commerce (Web services)
Peer-to-peer applications
The Semantic Web
Web Services—E-commerce Protocols and Services
17
The focus of Web services is on e-commerce. A Web service is built on existing and emerging
standards such as HTTP; Extensible Markup Language (XML); Simple Object Access Protocol
(SOAP); Web Services Description Language (WSDL); and Universal Description, Discovery,
and Integration (UDDI).
A Web service is an interface that describes a collection of operations that are network-accessible
through standardized XML messaging. A Web service is described, using XML notation, by its
service description. It covers all the details necessary to interact with the service, including
message formats that detail the operations, transport protocols, and location.
The goal of Web services is to enable companies to reduce the cost of participating in e-commerce
by standardizing the definition of common A2A communication. Companies may also consider
Web services as a distributed component model, enabling reuse within the enterprise.
The Web services architecture is based upon the interactions between three roles:
•
•
•
Service provider
Service registry
Service requestor
The interaction of these roles can be seen in Figure 2.5.
Figure 2.5. Role interaction in Web services.
The service provider is the platform that hosts access to the Web service. This is typically the
business process owner.
The service requestor is the application that is searching for, and invoking or initiating an
interaction with a service. This can be a person or a program without a user interface; for example,
another Web service.
The service registry is a searchable registry of service descriptions where service providers
publish their service definitions. Service requestors find services and obtain binding information
(in the service descriptions) for services during development or during execution.
18
The service description contains the details of the interface and implementation of the service.
This includes its data types, operations, binding information, and network location. The service is
the actual implementation.
The Core Services
The Web services architecture defines three core services:
•
•
•
The publish service enables the service provider to publish a service description, so the
service requestor can find, understand, and use the service.
The find/discovery service enables the service requestor to retrieve a service description
directly, or query the service registry for the type and description of the service required
for invocation.
The bind service enables a service requestor to invoke or initiate an interaction with the
service at runtime using the binding details in the service description.
These three operations—publish, find, and bind—are core to the Web services model. All three
services are provided in the higher layers of the OSI reference model.
Protocols
Now let's look at the protocols involved in the Web services architecture, as shown in Figure 2.6.
Figure 2.6. Protocols supporting Web services.
Web services are being built on common Internet protocols in addition to some emerging XMLbased encoding.
The SOAP layer represents the use of XML as the basis for the messaging protocol. SOAP is an
enveloping mechanism for communicating document-centric messages and remote procedure calls
using XML. It is basically an HTTP POST with an XML envelope as the payload. It defines a
mechanism to incorporate extensions to the HTTP message using SOAP headers and a standard
encoding of functions.
A SOAP message consists of three parts:
•
•
•
A mandatory SOAP envelope
An optional SOAP header
A mandatory SOAP body
The envelope is the top element of the XML document, representing the message.
The header is a mechanism for adding features to a SOAP message without prior agreement
between the communicating parties. SOAP defines a few attributes that can be used to indicate
who should deal with a feature and whether it is optional or mandatory.
19
The body is a container for mandatory information intended for the ultimate recipient of the
message. SOAP defines one element for the body: the fault element used for reporting errors.
SOAP messages support the publish, find, and bind services in the Web Services architecture.
SOAP provides the standard access protocol, which enables Web services to be integrated into
Web architectures and existing legacy architectures.
The next layer, WSDL, provides the standard for service definition. WSDL is an XML-based
service description language. WSDL defines the interface and mechanics of service interaction as
a set of endpoints operating on messages containing either document-oriented or procedureoriented messages.
The service interface defines WSDL elements that comprise the service description.
The portType element defines the operations of the Web service. The operations define what
XML messages can appear in the input and output data flows.
The message element is used to define the input and output parameters of each operation
identified in the portType element.
The types element is used to describe the use of complex data types within the message.
The binding element describes the protocol, data format, security, and other attributes for a
particular service interface.
Additional description is necessary to specify the business context, qualities of service, and
service-to-service relationships. The WSDL document is used with other service description
documents to describe higher-level aspects of the Web service. For example, business context is
described using UDDI data structures in addition to the WSDL document.
In summary, Web services are being built on the common protocols of the Internet protocol suite,
TCP/IP, and initially HTTP. In addition, bindings to other higher layer protocols, such as FTP and
SMTP, are being defined. Web services add SOAP as the common access protocol, and XMLbased service description protocols such as WSDL and UDDI. More information about SOAP and
WSDL can be found at the W3C consortium Web site at www.w3.org. Specific SOAP releases
can be found at the Apache Web site xml.apache.org/soap. Information relevant to UDDI can be
found at www.uddi.org.
Web services have gained considerable vendor support as a result of the involvement of IBM and
Microsoft. Microsoft is basing its new generation .Net applications on the Web services
architecture.
P2P Protocols and Services
The term peer-to-peer (P2P) indicates that two sides of a communication link use the same
protocol to conduct a networked conversation. Any computer can initiate a conversation with any
other computer. Peer-to-peer defines an application-to-application model that presents
communicating nodes as equals in terms of functional capabilities. It promotes a decentralized
model, where peer or service discovery is not confined to a specific node or role in the architecture.
This is illustrated in Figure 2.7.
Figure 2.7. P2P: A network of "equals."
20
P2P architectures have not been standardized above the transport layer, although most have
gravitated to exploiting the HTTP protocol. This provides P2P applications with simple universal
access across the Web. The services being offered by P2P applications can be classified into four
functional categories:
•
•
•
•
Managing and sharing information
Collaboration
Networking and Infrastructure
E-Commerce Enablement
Managing and Sharing Information
P2P services that distribute the management and sharing of computer resources across the network
are in this category.
File Sharing
With file sharing, each node in the network represents a potential repository for files. In effect, the
network becomes a distributed database of massive size and potential. Supporting protocols
provides management and discovery of resource locations and file transfers.
Resource Sharing
Complete resource sharing distributes computations out onto the network, which enables a node to
act as a general task manager. It can then collect and aggregate results. What makes the model
interesting is its capability to dynamically enlist worker machines to perform a common task.
Distributed Search Engines
The capability to query for specific information using distributed search engines has proven
valuable in navigating the vast amount of information that is available on the Internet. Search
protocols that use a divide-and-conquer approach to information discovery are popular.
Collaboration
P2P services that provide the organization and collaboration of groups with common interests or
goals are in this category.
Document Exchange Management
21
Products like Groove (www.groove.net) are promoting a secure document management and
exchange facility over public networks. Group collaboration is promoted by assigning levels of
trust to peer participants.
Instant Messaging
As opposed to e-mail, instant messaging products are offering real-time people-to-people
conversations over the Internet. These products have been heavily promoted and integrated into
commercial products. Additional features being developed include secure and reliable messaging.
Inter-Organizational Workflow
A new area of development for P2P is in the coordination of workflow processes within an
organization. The capability to dynamically broadcast changes in application state makes some
P2P models attractive for adaptation in process control applications.
Communities
Fundamental to P2P applications is the capability to discover peers who share a common interest.
Communities that dynamically form as a result of common interest or motivation are at the core of
many P2P applications.
Internet Gaming
Entertainment applications that provide group interaction are often encoded using P2P protocols
with a single task and game state coordinator.
Networking and Infrastructure
P2P services that provide the management and integration of applications with network resources
are in this category.
Integration with Web Services
P2P applications provide another integration model for Web services. As opposed to centralized
repositories, more decentralized implementations support consumer-oriented services.
Device management, service monitoring, and resource management applications are being
developed to support and complement current network management protocols.
E-commerce Enablement
A plethora of e-commerce enablement applications are being defined, such as:
•
•
•
•
•
•
Online auctions
Pricing and payment models
Trading hubs and e-commerce communities
Customer Care
Services for mobile users
Service personalization
Protocols
22
The protocols involved in the P2P model are as varied as the applications being designed, as seen
in Figure 2.8.
Figure 2.8. Lack of standards has led to application-specific protocol definition.
JXTA Protocol
Sun has recently released JXTA (juxtapose), which is an attempt to standardize the high-level
protocols being used in the P2P space. JXTA is transport- and language-agnostic; however, and
not surprisingly, Java and TCP/UDP are the areas of initial development.
In JXTA all protocols are defined as XML messages sent between two peers. JXTA messages
define the protocols used to discover and connect peers and peer groups and to access network
services offered by peers and peer groups.
Messages are sent between peer endpoints. A peer endpoint is a logical destination (embodied as a
URI) on any transport capable of sending and receiving datagram-style messages. Endpoints are
mapped into physical addresses by the messaging layer at runtime.
JXTA defines the following XML-encoded protocols:
•
•
•
•
•
•
Peer Discovery Protocol—enables a peer to find advertisements on other peers and can be
used to find any of the peer, peer group, or advertisements.
Peer Resolver Protocol—enables a peer to send and receive generic queries to find or
search for peers, peer groups, pipes, and other information.
Peer Information Protocol—enables a peer to learn about other peers' capabilities and
status.
Peer Membership Protocol—enables a peer to obtain group membership requirements,
such as an understanding of the necessary credential for a successful application to join
the group.
Pipe Binding Protocol—enables a peer to bind a pipe advertisement to a pipe endpoint,
thus indicating where messages actually go over the pipe. A pipe can be viewed as an
abstract, named message queue that supports a number of abstract operations such as
create, open, close, delete, send, and receive.
Peer Endpoint Protocol—enables a peer to ask a peer router for available routes for
sending a message to a destination peer.
Gnutella Protocol
Gnutella is a peer-to-peer file sharing and searching system that uses a decentralized control
model. It uses a broadcast messaging protocol for peer discovery. The message broadcasting
technique allows for the dynamic discovery of peers—systems running the Gnutella software also
known as servants—that are capable of responding to a Gnutella request. The protocol uses HTTP
as the underlying transport. Every message has a unique ID and defines the following functions:
23
•
•
•
•
•
Ping Request—This is sent by a client requesting a pong response from everyone on the
network. This message is propagated on the network until the message is either dropped
or returned to the originator.
Pong Response—This is sent in response to a ping request. This message contains the
host IP, port identification, a count of how many files the host is sharing, and the total
size of those files.
Client Push Request—This is for servants behind a firewall where the client cannot reach
the server directly. A push request message is sent, asking the server to connect out to the
client and perform an upload.
Search—A search message contains a query string, as well as the minimum network
speed that is acceptable to the client.
Search Response—This contains the IP address, port, and speed of the servant followed
by a list of file sizes and names and a unique ID that identifies the servant.
In summary, P2P applications are emerging as another model of networked applications that
exploit the decentralized nature of the Internet and leverage the vast array of resources that are
accessible. New application protocols are being defined on top of the common protocols of the
Internet protocol suite, which includes TCP/IP and HTTP.
Much like the workhorse protocols—FTP, Telnet, and SMTP—the new breed of protocols tend to
be application-specific. However, with JXTA, Sun is trying to define some level of
standardization. It is too early to determine the degree of success Sun will have with this effort.
Semantic Web Protocols and Services
The Semantic Web is trying to extend the existing Web architecture to enable computers and
people to work more efficiently. The proposed solution hinges on the use of metadata to describe
the data contained on the Web. Metadata is data about data. In the context of the Semantic Web, it
is data specifically describing Web resources.
The fundamental component of the Semantic Web is the XML-encoded Resource Description
Framework (RDF).
RDF is a foundation for processing metadata. It provides interoperability between applications
that exchange machine-understandable information on the Web. RDF can be used in a variety of
application areas including:
•
•
•
•
•
Resource discovery to provide search engine capabilities
Cataloging to describe the content and content relationships available at a particular Web
site, page, or digital library
Intelligent software agents to facilitate knowledge sharing and exchange
Content rating when describing collections of pages that represent a single logical
document
Intellectual property rights of Web pages, and for expressing the privacy preferences of a
user as well as the privacy policies of a Web site
RDF is also a model for representing named properties and property values. RDF properties can
be thought of as attributes of resources, and in this sense, correspond to traditional attribute-value
pairs. Additionally, RDF properties represent relationships between resources, so an RDF model
can resemble an entity-relationship diagram.
Entities described by RDF expressions are called resources. A resource identifier identifies
resources. A resource identifier is a URI plus an optional anchor ID.
A property is a specific attribute or relationship used to describe a resource.
24
A specific resource, together with a named property plus the value of that property for that
resource, is an RDF statement.
The statement
Robert Flenner is the creator of the resource at
http://www.jworkplace.org
is represented in RDF/XML as:
<rdf:RDF>
<rdf:Description about="http://www.jworkplace.org">
<s:Creator>Robert Flenner</s:Creator>
</rdf:Description>
</rdf:RDF>
A complete HTML document containing RDF metadata describing itself is as follows:
<html>
<head>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/metadata/dublin_core#">
<rdf:Description about="">
<dc:Creator>
<rdf:Seq ID="CreatorsAlphabeticalBySurname"
rdf:_1="Mary Andrew"
rdf:_2="Jacky Crystal"/>
</dc:Creator>
</rdf:Description>
</rdf:RDF>
</head>
<body>
<P>This is a fine document.</P>
Sc2
</body>
</html>
While RDF is the fundamental encoding of semantic information, the Semantic Web model
includes higher-level constructs, such as ontologies, to provide a common classification and
vocabulary for metadata definitions. An ontology provides a common vocabulary for classifying
and defining information or knowledge. It allows systems to share information in a structured and
meaningful way. Often artificial intelligence programs or software agents are used to process an
ontology and make decisions based on the content of the information. (See Figure 2.9.)
Figure 2.9. Semantic Web concepts.
25
RDF is not tied to any specific transport protocol, but does build on and attempt to complement
the current Internet protocols. RDF also relies heavily on the concepts of XML, Namespaces, and
URI-based addressing. One of the advantages to this approach for improving networked
applications is that it aligns with the current Web architecture using familiar constructs.
Existing Protocols in the Middleware Maze
The battleground above the transport layer is not new. Middleware, which includes services such
as messaging, Transaction Processing (TP) monitors, and Remote Procedure Calls (RPC), has
been involved in defining higher-layer services for years. These services provide added value in
building traditional distributed systems. Middleware fits into the OSI (Open Systems
Interconnection) model as shown in Figure 2.10.
Figure 2.10. The middleware domain also includes protocols above the transport
layer.
Middleware supports the application, presentation, and session layers, and might involve some
portion of the transport layer. The application, presentation, and session layers can contain:
•
•
Remote Procedure Calls (RPC)
Message Oriented Middleware (MOM) Systems
26
•
•
•
Object Request Brokers (ORB)
Transaction Processing (TP) monitors
Database access technologies, such as SQL Middleware
The Middleware domain is typically between the application and the interface to the operating
system and network services. However, it is often difficult to position middleware products
because their use and scope vary significantly.
Middleware Types
Middleware has also surfaced in a number of protocols at higher layers that must be understood in
the context of the networked application. (See Figure 2.11.)
Figure 2.11. Middleware protocols at higher layers are often product- and vendorspecific.
Message Oriented Middleware
Message oriented middleware (MOM) permits general message exchange using message queues.
It has become a popular approach to integrating inter- and intra-enterprise applications. The
message queuing facility enables clients and servers to communicate indirectly, which provides
several advantages.
Applications are provided a higher level of abstraction when dealing with network communication.
Destinations, or queues, can have logical names and be built up hierarchically.
Clients and servers do not have to be running at the same time to carry on a conversation.
Messaging products can therefore help support the disconnected client.
Traffic can be distributed or load balanced across multiple queues and multiple systems. This
provides a scalable solution to high throughput requirements.
Messaging queues are versatile. They enable you to define communication models that support
one-to-one or one-to-many patterns of message transfer.
MOM products offer various levels of message assurance, from best effort to guaranteed delivery.
This is provided through persistent queues that are capable of storing messages until
acknowledged.
Messaging products should continue to gain in popularity because of their broad range of
capabilities in application communication. However, these products are more prevalent within an
enterprise rather than between enterprises because of the proprietary protocols that often need to
be supported.
27
Remote Procedure Calls
Like messaging, a remote procedure call (RPC) provides communication between a client and a
server process. RPCs try to hide the intricacies of the transport by using ordinary procedure call
semantics. A client calls a remote procedure and suspends processing until it gets back a response.
Under the covers, an RPC runtime is capable of intercepting the call, and marshalling and
unmarshalling the parameters for network transport.
In a general sense, RPCs are used to describe Request-Reply message exchanges with basic datatype translation and connection-oriented communication services. It can also refer to products that
use an Interface Definition Language (IDL) to describe the argument lists for outgoing and
incoming parameters.
Many database management systems build RPC processing into their applications; in addition,
RPCs might be found in other products such as network operating systems and transaction
monitors.
RPCs are always synchronized. Web services rely heavily on RPC semantics because of the
single-request, single-response exchanges popular on the Web today.
CORBA (Common Object Request Broker Architecture)
The OMG (Object Management Group) defined the Common Object Request Broker Architecture
(CORBA) in an attempt to standardize the communication between distributed object systems.
The OMG has gone to great lengths to define common services required of distributed object
systems.
CORBA was defined to promote and enable interoperability between heterogeneous systems.
Applications can be written in different programming languages and still be able to communicate.
An Object Request Broker (ORB) provides a common software bus connecting distributed objects.
Requests are sent over the network using standard, on-the-wire IIOP (Internet Interoperability
Protocol) or GIOP (General Interoperability Protocol).
The OMG has defined a number of services to support the object request broker architecture.
These include everything from a Naming Service to an Object Transaction Service.
OMG defines standards at an interface level using a standard Interface Definition Language (IDL).
Implementers of the services are free to optimize different features and characteristics of the
service to best fit their target market.
CORBA-based architectures have been most prevalent in large enterprises, where heterogeneous
systems dominate.
Transaction Processing Monitors
TP monitors specialize in managing transactions from clients across one or more servers. When a
transaction ends, the TP monitor ensures that all systems involved in the transaction are left in a
consistent state. The classic example of the bank credit transaction followed by the bank debit
transaction is often cited to demonstrate the need for transactional integrity.
Transaction monitors are able to cross system boundaries to manage the transaction process. This
includes starting server processes, routing and monitoring their execution, and balancing their
workloads. In addition, TP monitors are used to guarantee the ACID properties to all programs
that run under their control.
28
The ACID properties are:
•
•
•
•
Atomicity—All the operations grouped under a transaction occur or none of them do.
Consistency—The completion of a transaction must leave the system in a consistent state.
If the transaction cannot achieve stable end-state, it must return the system to its initial
state.
Isolation—Transactions should not be affected by other transactions that execute
concurrently. Participants in a transaction should see only intermediate states resulting
from the operations of their own transaction, not the intermediate states of other
transactions.
Durability—The results of a transaction should be as persistent as the entity on which the
transaction commits.
TP monitors often use thread pools to assign work to available threads that act as a mechanism to
balance and control the load on any one server process. The TP monitor is able to funnel work to
shared server processes and act as a gatekeeper for the OS. This function—load balancing—is a
primary benefit to systems that must scale to support high transaction volumes. TP monitors are
able to establish priorities for tasks, and thus prioritize transactions within the system.
Fundamentally, the TP monitor assures ACID properties while enabling high throughput. This
alleviates much of the work application programmers would normally have to be concerned with,
such as network failures, concurrency, load balancing, and the synchronization of resources across
multiple nodes.
SQL Middleware
Unfortunately, while SQL has been standardized for years, vendor extensions and variations still
make access across products a challenge. Many middleware products exist to provide access to
multiple databases and transform SQL calls to a native database server's access language.
Typically, SQL middleware must deal with vendor-proprietary APIs across Windows, Linux,
Macintosh, and Unix clients. In conjunction with the API is a unique driver that handles runtime
calls and formats SQL messages. The driver typically provides a proprietary transport exchange
format with the server. Therefore, the format and protocols defined to access the database are
vendor-defined.
Most vendors support multiple protocol stacks that might include a common transport interface,
such as sockets or named pipes.
Even providing a common SQL interface still requires management of multiple vendor drivers. In
addition, although a common API exists, multiple formats and protocols are still supported under
the covers. A number of solutions, such as ODBC and JDBC, have emerged, but often run into
performance and scalability issues under high access scenarios. Gateway protocol converters are
often used, such as Remote Data Access (RDA) and IBM's Distributed Relational Data Access
(DRDA).
In summary, middleware has an established presence in most organizations. The protocols that
support middleware services often reflect the vendor's integration strategy. In other words, Oracle
middleware will offer Oracle database integration and IBM middleware will offer IBM product
integration to products such as CICS, DB2, and MQSeries.
The Protocol and Services Maze
One thing is clear: The number of protocols and services continues to expand. Integrating
applications with traditional middleware technologies has been difficult.
29
The introduction of Web services only presents a standard definition for defining integration
specifications, not a standard solution.
P2P systems are ripping into conventional techniques, but have not matured to the level necessary
to demonstrate clear direction and solutions.
The introduction of network-aware devices, such as cell phones and PDAs, over Wireless Access
Protocols (WAP) will further complicate an already complex networked application environment.
The rise of the "device market" is ushering in a host of new protocols geared not only at traditional
wide-area and local-area networks, but also new residential markets. For instance, standards such
as the Home Audio and Video Interoperability (HAVi) standard, the Home Phoneline Networking
Alliance (HomePNA), HomeRF, and the Universal Serial Bus (USB) are all developing standards
to communicate with devices. These devices all require management and service coordination.
The proliferation of technologies such as narrowband Internet access, as well as the faster digital
subscriber loop (DSL) and cable modem technologies, will further complicate compatibility with
market-specific devices and network management schemes such as DOCSIS and SNMP.
It is in this context that we introduce Jini as a framework for building order out of chaos in the age
of services and networked applications.
Jini—Building Order Out of Chaos
As mentioned in Chapter 1, Jini is about federating services on a network. In this respect, it shares
many of the concepts of the technologies reviewed in this chapter. Federating services enables us
to create logical groupings of services and then become a member of the federation. However, Jini
has matured far beyond the basic "find and bind" scenarios that have been covered to this point.
The Jini Services
Jini services can be classified as the following:
•
•
•
Core Services responsible for the creation and organization of the federation
Helper Services instrumental to providing a framework for good Jini citizenship
User Defined Services providing application-specific extensions to the Jini federation
Discovery Service
The discovery service enables us to find registries—LUS—in Jini and to locate service interfaces.
The discovery service is implemented by the discovery protocol. The discovery protocol relies on
a TCP/IP network that supports unicast and multicast messaging. Unicast and multicast messaging
will be discussed in detail in Chapter 4, "Building on Jini Foundation Concepts."
Join/Publish Service
Join is a form of discovery in that it requires a lookup service to be found and accessed in order to
publish service interface definitions. It uses similar unicast- and multicast-messaging mechanisms
to those just mentioned. Join and lookup are also discussed in detail in Chapter 4.
Event Service
The event service provides an interface to enable distributed event notification. Jini also provides
a helper service—the event mailbox service—that is used to enable store and forward-type
30
semantics for event notification. The mailbox service also provides critical support for
disconnected clients and services. The event mailbox service is discussed in Chapter 7, "The
Helper Services."
Leasing Service
Jini introduces the idea of a lease to service management. If you are a user or a provider of a
service, you must ensure that the resources you consume are managed through the leasing process.
All resources must have some management process that periodically checks to see whether
everything is OK, and to determine whether the resource is still an active participant.
Leasing is an important part of Jini infrastructure robustness. Leasing enables the LUS to delete
services that have failed to renew their lease. If you provide or are using a service, you must
periodically renew your lease. The renewal period depends on the application. You request a finite
renewal period on your lease, and the service provider determines the actual lease commitment.
Leasing is often referred to as providing the "self-healing" properties of a Jini network. Look for
the concept of leasing to enter into the definition of other service-based architectures!
Transaction Service
Jini defines a transaction interface and provides a reference implementation. Transactions are at
the core of many systems. The exchange of goods and services involves monetary transactions.
Transactions and transactional integrity are not new issues in distributed computing. However,
traditional solutions have tended to be heavyweight and complex. As the notion of distributed
transactions is expanded to include a broader array of clients, such as cell phones, Palm Pilots, and
mobile agents, we will require support for more flexible and lightweight transactions.
Jini uses the traditional two-phase commit transaction protocol to enforce the well-known ACID
properties in data manipulations. Database management systems go to great lengths to ensure
ACID properties. As a result, system overhead is generally proportional to the degree of
transactional integrity. More system resources are required to ensure data integrity in the light of
failures that might occur in reading, writing, and updating information.
The notion of data consistency with regard to partial failure is promoted through the two-phase
commit-based interface. But, the key is the degree to which the implementation will try to ensure
consistency despite a partial or catastrophic failure. By providing only an object interface to
transactions, Jini does not dictate implementation.
Lightweight implementations will reduce the overhead and time required to deploy transactional
systems. Similar to MOM, which provides various levels of message assurance, the new breed of
transactional systems will provide various levels of transactional integrity.
Persistence Service
Jini provides a persistence interface and a reference implementation called JavaSpaces.
JavaSpaces was heavily influenced by the concept of tuple spaces, which were first described in
1982 in the Linda programming language. The basic idea is that you can have many active
programs distributed over physically dispersed machines, unaware of each other's existence, able
to communicate by releasing tuples into the shared tuple space. Programs read, write, and take
tuples from the tuple space that are of interest to them.
JavaSpaces provides a mechanism to loosely couple systems through remote events and a
persistent shared-memory model. Processes can exchange information through this space and be
notified of changes through asynchronous events. JavaSpaces supports two implementation
models: transient spaces and persistent spaces. Transient spaces do not survive across a system
restart, while persistent spaces use secondary storage to save and restore services upon activation.
31
The simplicity of the JavaSpaces API provides an easy interface, yet it still provides a powerful
programming model.
Jini Protocols
Jini is different. Jini assumes it's a Java world. Although the specification does not require Java,
this is like saying you don't need wheels on your car to drive it. You can always pick it up and
carry it; however, that is clearly not the path of least resistance. The power of Jini shines through
when used in the homogeneous environment of Java and RMI. This provides the linchpin to
integrate the disparate technologies that seem to be flourishing out of control. (See Figure 2.12.)
Figure 2.12. Most Jini implementations rely heavily on Java and RMI.
The Jini services are defined using Java Interfaces. So, unlike the XML-encoded protocols of Web
services such as RDF, JXTA, and a host of other incarnations, Jini relies on Java bytecodes.
Essentially, you need a Java virtual machine (JVM) somewhere on your network to use Jini
effectively. In the next chapter, we will discuss setting up a Jini environment and concentrate on
RMI to demonstrate the power of the Jini underpinnings.
Bytecode
At the base level, Jini interfaces are defined using bytecode, which is the representation of a Java
class file to the JVM. It is what contains the instructions and definitions required for the JVM. It is
possible to generate Java bytecode without ever writing Java source code. However, this should be
considered the exception as opposed to the norm.
RMI-Marshalled Objects
Java objects that are transported across the network are wrapped in a MarshalledObject,
which serializes the object into a byte array. RemoteObjects within a marshalled object are
serialized as stubs. Stubs and skeletons will be explained in the next chapter.
Interfaces
As opposed to other networked applications, Jini applications are defined by Java remote
interfaces. XML is not used as a high-level interface definition, description, or protocol construct.
Interfaces provide for polymorphic changes in software evolution. Systems built on XML will
soon discover that XML only goes so far in defining system behavior. It is the behavior of the
exchange, not just the information that gets exchanged, that ultimately defines the application.
XML is a time bomb for service definition that has started ticking!
Entries
32
The Entry interface provides the basis for adding attributes to service definitions. When
services register with a lookup service, they can provide additional information to aid in client
search and selection.
Summary
Jini, unlike the other solutions discussed, is closely tied to a specific programming language. It is
very much a Java-centric solution to the networked application. Java has many benefits that make
it a contender in building service-oriented applications. It is proven, has a widespread distribution,
runs on multiple platforms, is object-oriented, and has built-in security.
Also unlike the other solutions, RMI provides Jini with the capabilities of mobile code "out-ofthe-box." This might prove to be one of the biggest differentiating factors in service capabilities.
33
Chapter 3. Supporting Technologies
IN THIS CHAPTER
•
•
•
The HTTP Server
Remote Method Invocation
Summary
Jini is about federating and organizing services on a network and allowing clients to discover and
use services. The Jini framework is designed to minimize the amount of human intervention
required to manage services.
When you first start establishing a Jini environment, you may doubt that the goal of minimal
human intervention was attained. There are a number of services that need to be started in order to
deploy a basic Jini network. The initial process can be difficult, especially if you are new to
remote method invocation (RMI), but after your network is established, the base services seldom
need administration or management.
There are three infrastructure services that will be required somewhere on your Jini network. They
are as follows:
•
•
•
An HTTP server that supports dynamic downloading of code
The Remote Method Invocation daemon (rmid) that provides the distributed remote
object lookup and activation mechanism, covered later in this chapter
The Jini LUS that provides the service registry and the service lookup capabilities on the
Jini network, which is covered in Chapter 4, "Building on Jini Foundation Concepts."
The HTTP Server
The HTTP server should not be new to you. Anyone who has surfed the Web has encountered an
HTTP server. It is based on support for the HTTP protocol, which is the standard protocol for
fetching Web pages across the Internet. Jini requires that an HTTP server be available on the
network—or a server capable of file transport (ftp)—because Jini uses this familiar protocol to
transport files (code) from machine to machine.
The HTTP Server is one of the first services that you will start when setting up your Jini network.
This enables Jini to transport downloadable code to applications when the applications need it.
You can run one or more HTTP servers on your Jini network. I recommend starting with a single
HTTP server. You will want to monitor when the code gets transported across the network by
various service requests. It is much easier to start and monitor a single instance of a service until
you are comfortable with the service interactions. It might then be appropriate to introduce
additional servers to support multiple service configurations, redundancy, scalability, and unique
application requirements.
The Role of the HTTP Server and Protocol
Any HTTP server should be able to satisfy the requirements of Jini. The simple and familiar GET
method of the HTTP protocol is used to request a specific file (URL) for download. We will
discuss the mechanics of requests in the section on RMI, but for now just recognize that requested
files are jar or class files necessary to instantiate objects to process distributed requests.
34
Jini-Supplied
The class com.sun.jini.tool.ClassServer that is supplied with the Jini distribution
implements a simple HTTP server for serving up jar and class files.
Note
You can download the Jini binaries and source code from the Java Developer Connection at
http://developer.java.sun.com/developer/products/jini. Appendix A, "Environment Set Up,"
provides the necessary links and resources for the Jini distribution.
The HTTP server can be started with the following command on Windows:
java -jar %JINI_HOME%\lib\tools.jar -port 8080 -dir %JWORK_HOME%\lib
-trees -verbose
where %JINI_HOME% references the Jini implementation directory, for example
C:\files\Jini1_1; and %JWORK_HOME% references the download directory that
contains the jar and class files that you will expose to clients. Any files that you want to make
available to clients or services should be placed in this directory location.
The other arguments include:
•
•
•
•
[-port port]
[-dir dir]
[-trees]
[-verbose]
The default port is 8080 and can be overridden with the -port option. The default directory on
Windows is J: and the default directory on non-Windows systems is /vob/jive/jars. The
default can be overridden with the -dir option. By default, all files under the default directory
(including all subdirectories) are served up via HTTP.
If the -verbose option is given, then all attempts to download files are output to the console.
The -trees option can be used to serve up individual files stored within jar files, in addition to
the files that are served up by default. If this option is used, the server searches all jar files in the
top-level directory. If the name of the jar file is name.jar, then any individual file named
within that jar file can be downloaded using a URL in the form of http://host:port/name/file.
When this option is used, an open file descriptor and cached information are held for each jar file,
for the life of the process.
In the examples, I've set the following Windows variables to define the location of the Jini install
directory and the location of files available via the HTTP server. Your setup will probably be
different.
set JINI_HOME=D:\files\jini1_1\
set JWORK_HOME=C:\JiniJavaSpaces\services\lib
GUI-Activated
35
Jini also comes with a GUI-supplied service activation program. It enables you to enter the
required and optional parameters to start Jini services in a tabbed pane as an alternative to scripts.
The following command starts the service launcher interface. From the command prompt, type the
command:
java -cp %JINI_HOME%\lib\jini-examples.jar
com.sun.jini.example.launcher.StartService
To use the launcher, you can open and edit the properties file located under the /jini1_1/
examples/launcher directory from the file menu. This will generate the tabs shown in
Figure 3.1 and enable you to customize the parameters to your environment. There are properties
files for both Unix and Windows that supply a list of default values.
Figure 3.1. A graphical view to starting and stopping Jini services.
After you load the appropriate properties file, your display should look similar to this:
If you are new to Jini, you can use this interface to examine the basic parameters that are available
to start Jini services in addition to rmid and the HTTP server. This will help you become familiar
with the commands that are used to start the various services.
To start the Web server supplied with Jini, simply select the WebServer tab and set the following
properties, as seen in Figure 3.2:
•
•
•
Executable jar file: Specify the path to Jini tools.jar
Port: Specify the port on which to run the HTTP server
Document Area: Specify the path from which to serve jar files
Figure 3.2. Example Web Server parameter settings.
36
Now select the Run tab and start the Web server. On the console you will see the command
invoked and a list of the files that are in the directory pointed to by the -dir argument of the
Document Area property.
If the service failed to start, you might want to check the "Gotchas and Common Failures" section
in Appendix A to troubleshoot the problem.
Alternatives
As mentioned at the start of this section, any HTTP server should be able to perform the functions
required to support Jini. In fact, the Jini HTTP server implementation is a minimalist approach to
satisfying the startup requirements.
If you already have a Web server, check your server documentation to determine the necessary
configuration parameters to access files. This is usually nothing more than a parameter in a startup
script indicating a root directory or path where you place Web files.
Now let's take a look at another infrastructure service: remote method invocation (RMI). RMI is
often the source of confusion when establishing a Jini environment. For this reason, it is important
to understand how RMI fits into the Jini architecture and ensure that you understand the
dependencies between RMI and the Jini environment.
Remote Method Invocation
RMI enables Java applications running in different Java Virtual Machines (JVM) to communicate.
Whether these JVMs exist on the same host machine or on different machines does not matter. Of
course, this is not new to distributed processing. Just like RPC (Remote Procedure Calls), the
processes need not exist in the same address space or even on the same machine. However, RMI is
able to offer a number of advantages over other distributed models because it assumes the
environment is Java-enabled.
37
The Java platform's remote method invocation system has been specifically designed to operate in
the Java application environment. The RMI system assumes the homogeneous environment of the
JVM; therefore, the system can take advantage of the Java platform's object model.
Client applications invoke local calls through a stub interface that communicates with the actual
remote object as illustrated in Figure 3.3. The RMI runtime performs all the necessary
communication housekeeping to ensure that two processes running on separate JVMs can
exchange invocation requests and results through an exposed common interface definition.
Figure 3.3. Client invocation using RMI.
In the Java platform's distributed object model, a remote object is described by one or more
remote interfaces, which are interfaces written in the Java programming language. A remote
interface is a Java interface that extends the java.rmi.Remote interface and defines the
methods, which are all made available to remote clients. All methods declared in the interface that
can be invoked remotely must declare that they throw a java.rmi.RemoteException.
For example, the following simple Agent interface extends Remote, and the talk method
throws RemoteException. The object that implements this interface is exposing its talk
method to clients in other JVMs.
Listing 3.1 The Agent Interface
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Agent extends Remote {
public String talk() throws RemoteException;
}
A client with a reference to the Agent can invoke the talk method regardless of where the
Agent implementation physically resides. Of course, there are network and security
considerations that impact this capability. Firewalls are one of the most frequently encountered
inhibitors to RMI communication. Security will be discussed in detail in Chapter 9, "Security in
Jini."
The RMI Registry
In the previous chapter, you saw the importance of discovery to the emerging networked
applications. So how are remote objects in RMI discovered?
38
The remote object registry (RMI registry) is a repository for associating a name with a remote
object. The java.rmi.Naming class provides methods for storing and obtaining references
to remote objects in the rmiregistry. The Naming class's methods (bind, unbind,
rebind) take as one of their arguments a name that is a URL-formatted
java.lang.String of the form:
[rmi:][//][host][:port][/name]
where:
•
•
•
•
rmi names the protocol and may be omitted
host is the host (remote or local) where the registry is located,
port is the port number on which the registry accepts calls,
name is a string that represents the remote object.
Both host and port are optional. If host is omitted, the host defaults to the local host. If
port is omitted, then the port defaults to 1099.
Binding a name to a remote object associates the name with the remote object. If you have used
other distributed object systems such as CORBA, this process should not be new. A remote object
can be associated with a name using the Naming class's bind or rebind methods. For
security reasons, the Naming.bind, Naming.rebind, and Naming.unbind methods
can only be executed on the same host as the RMI registry.
After a remote object is registered (bound) with the RMI registry on the local host, callers on a
remote (or local) host can look up the remote object by name, obtain its reference, and then invoke
remote methods on the object.
Unicast Servers
RMI provides convenience classes that remote object implementations can extend to facilitate
remote object creation. These classes are
java.rmi.server.UnicastRemoteObject and
java.rmi.activation.Activatable.
A remote object implementation must be exported to RMI. Exporting a remote object makes that
object available to accept incoming calls from clients. For a remote object implementation that is
exported as a UnicastRemoteObject, the exporting involves listening on a TCP port for
incoming calls. More than one remote object can accept incoming calls on the same port. A
remote object implementation can extend the class UnicastRemoteObject to make use of
its constructors that export the object, or it can export the object via
UnicastRemoteObject's static exportObject methods.
Listing 3.2 The UnicastRemoteObject Class
public class UnicastRemoteObject extends RemoteServer{
protected UnicastRemoteObject()
protected UnicastRemoteObject(int port)
protected UnicastRemoteObject(int port, RMIClientSocketFactory
csf,
RMIServerSocketFactory
ssf)
public Object clone()
public static RemoteStub exportObject(Remote obj)
39
public static Remote exportObject(Remote obj, int port)
public static Remote exportObject(Remote obj, int port,
RMIClientSocketFactory csf, RMIServerSocketFactory
ssf)
public static boolean unexportObject(Remote obj, boolean force)
}
The no argument constructor of the UnicastRemoteObject, creates and exports a remote
object on an anonymous port chosen at runtime. The second form of the constructor takes a single
argument, port, that specifies the port number on which the remote object accepts incoming
calls. The third constructor creates and exports a remote object that accepts incoming calls on the
specified port via a ServerSocket created from the RMIServerSocketFactory.
Clients make connections to the remote object via Sockets supplied from the
RMIClientSocketFactory. You will make use of socket factories to change the
underlying socket behavior to support additional connection features, such as security and
encryption. Chapter 9 will examine customizing socket connections.
Let's run through an example that demonstrates the power of RMI and, in the process, prepares
your environment for Jini.
Building an RMI Non-Activatable Service
You will define, build, and test a command processor that will enable you to invoke commands on
a remote machine and return an object that represents the command's result. The steps involved
include the following:
1. Define the example command processor.
Define the remote interface (in this example, the Shell interface that extends
java.rmi.remote).
Create an RMI service implementing the remote interface.
Create a client application and example command.
2. Build the example command processor, demonstrating the necessary files required.
Compile the files.
Create the required stub interfaces.
Create jar files (client-side and server-side).
Define the security policy files for client and server.
3. Test the example command processor and supporting RMI environment.
Start the RMI registry.
Start the RMIShell server.
List the registry contents to verify setup.
Test the client.
40
Define Remote Interface (extend java.rmi.remote)
Interfaces are important to Jini and RMI. You'll build a command processor that will enable us to
invoke commands on any machine on the network using the Shell interface.
You'll use the familiar Command pattern (see Design Patterns by Erich Gamma, et al. for more
information) to define an interface that has an execute method and returns an object. This
enables us to define a simple but powerful interface. Notice we are not extending the
java.rmi.Remote interface, but rather Serializable.
Listing 3.3 The Command Interface
public interface Command extends java.io.Serializable {
public Object execute();
}
The Command interface will be used as a parameter of the remote interface Shell as seen in
the following code. Therefore, the Command interface is not the interface that you expose and
invoke remotely. Instead, it will be used as a parameter to the remote interface. Parameters that are
defined in the method signatures of an interface must be serializable so that they can be serialized
(converted into byte streams) and transported between virtual machines. Additionally, parameters
that implement remote interfaces are passed as stubs (references) to the calling process.
The remote interface Shell takes a Command object as an argument, extends
java.rmi.remote, and throws RemoteException.
Listing 3.4 The Shell Interface
public interface Shell extends java.rmi.Remote {
public Object executeCommand(Command cmd) throws
RemoteException;
}
The two interfaces, Shell and Command, are delivered to developers of clients and servers of
the command processor. This is all that would be required to enable developers anywhere in the
world to start developing commands that would work in the processor framework.
Of course, the more developers building and sharing Command objects, the better. One hundred
developers building command processors based on this interface would make things both
interesting and exciting. One thousand developers building and linking command processors
around the globe—well, we just don't know!
Create RMI Service That Implements Remote Interface
Let's start with a simple command processor, RMIShell. RMIShell implements the Shell
interface and extends UnicastRemoteObject.
Listing 3.5 The RMIShell Class
// Our Command Processor Class
public class RMIShell extends UnicastRemoteObject implements Shell
{
public RMIShell() throws RemoteException {
super();
41
}
As mentioned previously, the UnicastRemoteObject class defines a remote object whose
references are valid only while the server process is running. The UnicastRemoteObject
class provides support for point-to-point active object references using TCP streams.
Because you are extending UnicastRemoteObject, the object is automatically exported to
the RMI runtime during construction. Inheriting from UnicastRemoteObject eliminates
the need to call exportObject.
Let's look at the main method for RMIShell.
Listing 3.6 The RMIShell Class Continued
// Our service requires remote behavior and extends
UnicastRemoteObject
import java.rmi.server.UnicastRemoteObject;
// Our interface definitions
import org.jworkplace.command.Command;
import org.jworkplace.command.Shell;
public static void main(String[] args) {
if(args.length < 1) {
System.out.println("usage [hostname]");
System.exit(1);
}
// set the hostname from the 1st argument
String hostname = args[0];
// must set a security manager to load classes
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
Shell shell = new RMIShell();
// prepare to bind the server with a name
String name = "//" + hostname + "/RMIShell";
Naming.rebind(name, shell);
System.out.println("RMIShell bound");
} catch (Exception e) {
System.err.println("RMIShell exception: " +
e.getMessage());
e.printStackTrace();
}
}
// implementation of the Shell interface
public Object executeCommand(Command cmd) {
return cmd.execute();
}
}
The first thing you must do is install and set an appropriate security manager that protects access
to system resources from untrustworthy downloaded code. Forgetting to do this will result in a
runtime error, which will throw a java.lang.SecurityException. The security
manager determines whether downloaded code has access to the local file system or can perform
any other privileged operations.
42
All programs using RMI must install a security manager, or RMI will not download classes other
than from the local classpath in remote method calls. This restriction ensures that the operations
performed by downloaded code go through a set of security checks. In addition to installing a
security manager, you also define an associated security policy.
grant {
permission java.security.AllPermission "", "";
};
Security will be examined in detail in Chapter 9. For now, just ensure that you have a policy file
available that grants all permissions, similar to the one shown previously. You can save it in a
working directory as policy.all. This should only be used in a test situation where access to
outside networks is nonexistent or well-controlled. You will eventually set the policy file
controlling the process at runtime, by setting a property on the command line, when you start the
service.
After you have installed a security manager and exported the remote object to RMI, you need to
associate a name to the object so that clients can find the service. As mentioned before, this is
done by using the java.rmi.Naming class. The Naming class takes a name that is a URLformatted java.lang.String of the form:
[rmi:][//][host][:port][/name]
A sample fragment would look like the following:
String name = "//hostname/RMIShell";
Naming.rebind(name, shell);
This code fragment associates the name RMIShell with the remote object running on the
designated host machine. The port is optional and defaults to 1099, which is the default port for
the RMI registry.
The only thing left necessary to complete our service is to implement the Shell interface.
public Object executeCommand(Command cmd) {
return cmd.execute();
}
The implementation invokes the Command interface's execute method and returns the
resulting object to the client. Of course, the server has no idea what the command actually is,
because it is passed to the server as a parameter in the method invocation. In this case, the client is
providing the server with the command to execute. Because we have not set any security, almost
any command could be passed, which is a dangerous proposition.
Let's look at how the client code is developed.
Create a Client Application and Example Command
The client implements the CommandLine class and also defines a class that implements the
Command interface.
Listing 3.7 The CommandLine Class
import java.rmi.*;
import org.jworkplace.command.*;
43
public class CommandLine {
public static void main(String args[]) {
if(args.length < 2) {
System.out.println(" usage [hostname] [remote directory]");
System.exit(1);
}
// even clients must set a security manager
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
// we pass the hostname in as an argument and perform lookup
String name = "//" + args[0] + "/RMIShell";
Shell shell = (Shell) Naming.lookup(name);
// we define a class that implements the Command interface
// argument 1 represents a directory on a remote machine
FileList command = new FileList(args[1]);
// execute command and cast result object
String[] files = (String[]) (shell.executeCommand(command));
// error invalid directory supplied
if(files == null) {
System.out.println("Invalid directory");
} else {
// display results to console
for(int i=0; i<files.length; i++)
System.out.println(files[i]);
}
} catch (Exception e) {
System.err.println("FileList exception: " +
e.getMessage());
e.printStackTrace();
}
}
}
The CommandLine class first installs a security manager. Like the server-side development,
you will also need a very liberal client-side security policy file:
grant {
permission java.security.AllPermission "", "";
};
You will use the java.rmi.Naming.lookup() method to get a reference to a remote
object. This is the same name that was supplied to the server when it was registered:
//hostname/RMIShell.
The RMI runtime will return an instance of the Shell remote interface. This is actually a
reference to a remote interface that the local stub object will use to resolve to the remote
implementation object when the remote object is accessed by calling the executeCommand
method.
The command object developed in this example is a FileList object.
44
Listing 3.8 The FileList Class
import java.io.File;
import org.jworkplace.command.*;
// implements our serializable Command interface
public class FileList implements Command {
private String path;
// we paramaterize the command with a path
public FileList(String path) { this.path = path; }
// invoke the command and return object
public Object execute() {
File file = new File(path);
// simple file list
return file.list();
}
}
The FileList class implements the Command interface. The constructor takes a directory
path as a parameter. The execute method creates a File object and returns a String[]
array of file and directory names.
On the command line, you supply the remote directory name, which in this case is args[0].
This enables us to get a directory listing from a remote machine. The remote machine executes the
command locally and returns the result to the client. The remote machine does not know the
command that is being executed. In fact, the remote machine does not even have the FileList
class available.
This might be a good point to stop and look at the sequence of exchanges that are supporting the
command processor, as illustrated in Figure 3.4.
Figure 3.4. Sequence of exchanges in the Filelist command example.
45
1. The RMIShell registers with the RMI registry using the RMI protocol.
2. The RMI registry uses the HTTP server identified by the codebase property of the startup
script to download the required files (for instance, RMIShell_Stub.class in
rmishell-dl.jar) using the URL protocol.
3. The client retrieves the remote reference to the RMIShell interface by invoking the
java.rmi.Naming class to resolve the name-to-object reference.
4. The client downloads the required files from the server-side HTTP server to invoke the
service.
5. The client invokes executeCommand, which is processed locally by the stub class
RMIShell_Stub. RMIShell_Stub and the RMI runtime marshals (formats) the
parameters necessary to invoke the command remotely and sends the request using the
RMI protocol.
6. The RMIShell implementation (actually the RMI runtime classloader) requires the
definition of the FileList class from the client and accesses the class definition from
the client-side HTTP server using the URL protocol.
As you can see, there is a lot of communication going on under the covers in this scenario. It
requires an HTTP server on the client-side and the server-side to satisfy requests for missing class
definitions. This capability to dynamically move code to the point of execution demonstrates a
significant difference between RMI and other distributed object systems.
The client contacted the HTTP server on the implementing host and required the remote stub
interface (RMIShell) to download the required classes. The server required the definition of the
command FileList and contacted the client-side HTTP server to download the required
classes.
You could have also defined a scenario where a parameter passed would have been implemented
as a Remote interface. In this case, a stub (reference) would have been downloaded from the
client and a remote request from the server to the client would have resulted. In effect, a callback
to the client would have made the server a client of the client.
Compile the Files
Change to the chapter3 directory, where you copied the examples for this book; for example,
C:>cd \chapter3\example1. You should see the following Java files along with other
files that will help you build the service:
Command.java
CommandLine.java
FileList.java
RMIShell.java
Shell.java
There is a compile.bat file included that will compile the files. Simply invoke:
46
C:\chapter3\example1>compile
This should create the necessary class files in the current directory and the stub file
RMIShell_Stub.class.
Create the Required Stub Interfaces
The RMI stub protocol is used by the stub to communicate with the server. To generate a stub, you
must invoke the rmic compiler for the remote service you are defining; in this case RMIShell.
The supplied compile.bat file invokes the rmic to generate the stub file for the RMIShell
class. You'll be using the 1.2 version of rmic because we are assuming JDK 1.2 or later support
across the environment.
Create Jar Files (Client-Side and Server-Side)
There is a jars.bat file included in the directory that generates the necessary jar files for
deployment. Simply invoke
C:\chapter3\example1>jars
You should study the commands in the jars file, so that you understand how the jar files are
created, and perhaps more importantly, why.
The jar files generate the following:
•
service.jar— Contains the RMIShell command processor and supporting
classes.
•
•
•
service-dl.jar— Contains the RMIShell_Stub class and supporting classes.
client.jar— Contains the CommandLine client and supporting classes.
client-dl.jar— Contains the Command and the FileList classes.
The jars.bat also uses manifest files to identify the main class in each jar file (client—service)
to be invoked at runtime. As a general rule, you will use the -jar option when invoking java.
This helps to alleviate classpath problems by ensuring that all files are accessed from jar files as
opposed to current classpath settings. Getting into this habit of construction will help in your
future development.
Copy the command-dl.jar file to the client HTTP directory. This is the same location that
you used to start the HTTP server.
Copy the client-dl.jar and service-dl.jar file to the appropriate HTTP
directories: the client-dl.jar to the client HTTP server directory and the service-dl
to the service HTTP directory, as seen in Figure 3.5.
Figure 3.5. Client and server HTTP directory display.
47
Define the Security Policy Files for Client and Server
Create the policy.all file for both the server and the client:
grant {
permission java.security.AllPermission "", "";
};
These files will set the security permissions to use for the JVM when you run the Java programs.
Start the RMI Registry
On the server, unset the classpath and start the RMI Registry:
C:\>setenv CLASSPATH=
C:\> rmiregistry&
Try to use the same directory every time you start the RMI Registry until you are sure that your
environment is set up properly. Also, avoid starting the registry in the same directory in which you
are about to run your server. The classpath for the downloaded jar files will not be set properly if
the required files are found in the current directory. If you use the convention (as shown) of
always creating jar files, this should eliminate some of the potential problems.
Start the RMIShell Server
Go to the directory where the RMIShell has been developed. Start the RMIShell command
processor. In this example there is a "service" directory under example1 that contains a
startService script.
The script defined for Windows is startService.bat. It is invoked by the command:
C:\JiniJavaSpaces\chapter3\example1\service>startService
remote_host_name
The remote_host_name argument is the hostname or IP address where the RMI Registry is
running. For instance, you would invoke startService 172.16.1.2 if you were
running RMI Registry on IP address 172.16.1.2.
48
The startService script requires that an environment variable, HTTP_ADDRESS, be set
prior to running the script. The HTTP_ADDRESS is the location of the running
HTTP_SERVER. For example:
set HTTP_SERVER=172.16.1.2:8081
would indicate that the HTTP server is running on the host address 172.16.1.2 at port 8081.
The startService is defined as follows:
Listing 3.9 The startService.cmd script
@echo off
if not "%HTTP_ADDRESS%" == "" goto gotServerHTTP
echo You must set HTTP_ADDRESS to point at your service HTTP server
echo For example set HTTP_ADDRESS=[hostname][:port]
goto exit
:gotServerHTTP
java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all Djava.rmi.server.codebase=http://%HTTP_ADDRESS%/service-dl.jar
service.jar %1
:exit
You will notice the script requires that two additional parameters be set— Djava.security.policy and the -Djava.server.codebase.
The -Djava.security.policy parameter is set to the server-side location of the
policy.all file. The script defaults to the Jini distribution file at
\policy\policy.all.
The -Djava.rmi.server.codebase parameter is set to the server-side HTTP server
address (hostname:port) and the jar file (service-dl.jar) you make available to
clients.
You should see on the HTTP console the access to the service-dl.jar file. If you do not
see this access, then you might have a classpath problem. The registry should gain access to the
class definitions using the HTTP server, and not on the local classpath. If the registry does not use
the HTTP server, you need to verify that the classpath was not set when you started the RMI
registry. Stop the registry and verify.
List the Registry Contents to Verify Setup
There is a list registry program that you can use on the server to verify that the RMIShell service
is registered and active as RMIShell in the registry. The program is ListRMIServices
and is in the directory \utilities.
Copy the program to a directory on a machine that has access to the RMI registry. To verify, run
the program using the following command:
C:\JiniJavaSpaces\utilities\java ListRMIServices
You should see the service listed on the console as:
49
rmi://:1099/RMIShell
Test Client
To test the client, move to the directory and machine where you deployed the client software.
Run the following client command:
C:\JiniJavaSpaces\chapter3\example1\client>startClient
remote_host_name
remote_host_directory
Where -Djava.security.policy is the path to your policy file, and Djava.rmi.server.codebase is set to the client-side HTTP server address
(hostname:port) and the jar file you will make available to servers.
remote_host_name is the name of the remote host, and remote_ host_directory
is the remote directory to search and display files.
Again the script requires that the environment variable for HTTP_ADDRESS be set to the
location of the client-side HTTP server. For example, on the command line type:
set HTTP_SERVER=[hostname][:port]
where hostname is the name or IP address of the machine running the client-side HTTP server;
and :port is the port number you assigned or the default 8080.
If you are trying this example on a single machine, you will have to change either the server or
client port assignment; otherwise, a port conflict will result.
You will see the contents of the remote directory displayed on the console.
You have just overcome the first major hurdle in establishing an RMI-Jini environment.
Congratulations!
Building on the Java 2 Platform
With Java 2, RMI defined two types of service based on activation policy—activatable and nonactivatable. A non-activatable service is one that you must manually start—like the RMIShell
service you just defined.
RMI also supports activatable services. An activatable service is one that RMI can start
automatically, depending on its configuration. RMI has the capability to restart services when it is
activated or to start services when they receive their first incoming call. This automatic activation
is instrumental to enabling a more robust distributed environment. An activation daemon takes
care of restarting, deactivating, and reactivating services. As I'll demonstrate, Jini uses this
environment in a number of interesting ways.
The Intelligent Socket
The next approach to writing RMI services is by extending Activatable as opposed to
UnicastRemoteObject as seen in Figure 3.6. There are a number of key components to
the activation model that should be highlighted. Because many of the Jini services are activatable
services, this background will be beneficial.
50
Figure 3.6. Class hierarchy for remote objects.
The java.rmi.activation.Activator interface provides the basic functionality of
activation. The Sun implementation of this interface is provided through the
sun.rmi.server.Activation. GroupImpl and is invoked by starting the RMI
daemon process.
The rmid is a daemon process that is started when you reboot your system. The typical command
to start the process is:
rmid –J-Dsun.rmi.activation.execPolicy=none
Of course, this is bypassing security constraints and continues with the theme in this chapter of
gratuitous security policies—a theme that will change in subsequent chapters!
In addition to the Activator, other key elements of the activation model include:
•
•
•
The java.rmi.activation.ActivationGroup, which is a group of
services that have been identified to share a common JVM.
The java.rmi.activation.ActivationMonitor, which tracks and
monitors the state of an object in an activation group and the state of the activation group
as a whole.
The java.rmi.activation.ActivationSystem, which provides the
interface to register activatable objects and groups.
Building an RMI-Activatable Service
Let's outline the changes necessary to make the RMIShell an activatable service.
51
1. Change inheritance from UnicastRemoteObject to Activatable.
2. Create an activation group.
3. Create an activation object description.
4. Register the activation description with rmid.
5. Bind the stub to the registry.
The RMIActivatableShell class provides an example of an RMI activatable service.
Listing 3.10 The RMIActivatableShell Class
package org.jworkplace.service;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import
import
import
import
import
import
import
import
java.util.Properties;
java.rmi.activation.Activatable;
java.rmi.activation.ActivationDesc;
java.rmi.activation.ActivationID;
java.rmi.activation.ActivationGroup;
java.rmi.activation.ActivationGroupDesc;
java.rmi.activation.ActivationGroupID;
java.rmi.MarshalledObject;
import org.jworkplace.command.*;
// extend Activatable
public class RMIActivatableShell extends Activatable
implements Shell
{
public RMIActivatableShell(ActivationID id, MarshalledObject data) throws
RemoteException {
// register object with activation system using anonymous port
super(id, 0);
}
// our implementation
public Object executeCommand(Command cmd) {
return cmd.execute();
}
public static void main(String[] args) {
if(args.length < 3) {
System.out.println("usage [hostname] [codebase] [policy]");
System.exit(1);
}
String hostname = args[0];
String codebase = args[1];
String policy = args[2];
// set security manager
try {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
52
}
// create the activation group description
Properties props = new Properties();
props.put("java.security.policy", policy);
// set class path
props.put("java.class.path", System.getProperty("java.class.path"));
props.put("java.rmi.server.codebase", codebase);
ActivationGroupDesc agd = new ActivationGroupDesc(props, null);
ActivationGroupID agi = ActivationGroup.getSystem().registerGroup(agd);
ActivationGroup.createGroup(agi, agd, 0);
// create the activation object description
ActivationDesc ad = new ActivationDesc("org.jworkplace.service.
RMIActivatableShell", codebase, null, true);
// register the activation description with rmid
Shell shell = (Shell) Activatable.register(ad);
// bind the stub to the registry
String name = "//" + hostname + "/RMIActivatableShell";
Naming.rebind(name, shell);
System.out.println("RMIActivatableShell bound");
System.exit(0);
}
catch (Exception e) {
System.err.println("RMIActivatableShell exception: " +
e.getMessage());
e.printStackTrace();
}
}
}
Change Inheritance From UnicastRemoteObject to Activatable
The first change is to redesign the class hierarchy so that the new shell inherits from
Activatable. The Activatable constructor will export the object just like
UnicastRemoteObject did.
The activation system will call the constructor with a unique activation ID and a marshalled object
that contains any information that you want to pass to the activated object. This information is
passed when you register the service, as illustrated in the following:
public class RMIActivatableShell extends Activatable
implements Shell
{
public RMIActivatableShell(ActivationID id, MarshalledObject data) throws
RemoteException
{
// register object with activation system using anonymous port
super(id, 0);
}
Create an Activation Group Description
53
An activation group descriptor contains the information necessary to create and re-create an
activation group in which to activate objects. The description contains:
•
•
•
The group's class name
The group's code location (the location of the group's class)
A "marshalled" object that can contain group-specific initialization data
The group's class must be a concrete subclass of ActivationGroup. A subclass of
ActivationGroup is created/re-created via the ActivationGroup.createGroup
static method that invokes a special constructor that takes two arguments—the group's
ActivationGroupID, and the group's initialization data (in a
java.rmi.MarshalledObject).
You construct an activation group descriptor that uses the system defaults for group
implementation and code location. Properties specify Java environment overrides (which will
override system properties in the group implementation's JVM). The command environment can
control the exact command/options used in starting the child JVM, or can be null to accept
rmid's default.
// set the policy and codebase properties to start the group
Properties props = new Properties();
props.put("java.security.policy", policy);
props.put("java.rmi.server.codebase", codebase);
// use the rmid default command environment
ActivationGroupDesc agd = new ActivationGroupDesc(props, null);
// get the activation group id
ActivationGroupID agi =
ActivationGroup.getSystem().registerGroup(agd);
// create the group
ActivationGroup.createGroup(agi, agd, 0);
Create an Object Activation Description
You've just created a group description, now you need to create a specific object description. An
activation descriptor contains the information necessary to activate an object, which includes the
following:
•
•
•
•
•
The object's group identifier
The object's fully qualified class name
The object's code location (the location of the class) and a codebase URL path
The object's restart mode
A marshalled object that can contain object-specific initialization data
A descriptor registered with the activation system can be used to re-create/activate the object
specified by the descriptor. The MarshalledObject in the object's descriptor is passed as
the third argument to the remote object's constructor for objects to use during
reinitialization/activation.
ActivationDesc ad = new
ActivationDesc("RMIActivatableShell", // class name
codebase, // codebase
null,
// no marshalled data passed
true); // restart
54
Register and Bind
Register the object with rmid and then bind the name to the registry.
Shell shell = (Shell) Activatable.register(ad);
String name = "//" + hostname + "/RMIActivatableShell";
Naming.rebind(name, shell);
That is all that is required to create an activatable object. As a result of the changes, you now have
an object implementation that can be created on demand, and if a failure occurs, will be
automatically started when the rmid process is restarted.
Let's return to the GUI-supplied service activation program so you can start rmid and test your
activatable service. To start rmid, simply select the RMID tab and set the following properties, as
shown in Figure 3.7:
•
•
RMID command: rmid
Options: -J-Dsun.rmi.activation.execPolicy=none
Figure 3.7. Parameters to start the rmid application.
Then, select the Run tab and start rmid. The activation daemon, when started, silently executes
unless a parameter for debug information has been set, or something goes wrong! If the service
fails to start, you might want to check the "Gotchas and Common Failures" section in Appendix A
to troubleshoot the problem.
When rmid starts, it creates a log directory and file used to record the state of the activation
system that rmid manages.
Now let's build and test our RMI-activatable Service. The compilation and stub generation is
similar to the non-activatable shell you just created.
55
Compile the Files
Change to the chapter3 directory where you originally copied the examples for this book; for
example, C:>cd \chapter3\example2. You will see the following files:
CommandLine.java
FileList.java
Command.java
Shell.java
RMIActivatableShell.java
Invoke the java compiler:
D:\chapter3\example2>compile.bat
Create the Required Stub Interfaces
The RMI stub protocol is used by the stub to communicate with the server. To generate a stub you
must invoke the rmic compiler for the remote service you are defining; in this case
RMIActivatableShell. The supplied compile.bat file invokes the rmic compiler to
generate the stub file for the RMIActivatableShell class.
Create Jar Files (Client-Side and Server-Side)
There is a jars.bat file included in the directory that will generate the necessary jar files for
deployment. Simply invoke:
C:\chapter3\example2>jars
The same procedure should be followed as in example1 for coyping the jar files to the
appropriate directories. You will replace the current jar files. See example1 earlier in this
chapter for details.
Define the Security Policy Files for Client and Server
The policy file setup is the same as in example1.
Start the RMI Registry
On the server, unset the classpath and start the RMI registry.
\usr\files\JWorkPlace>setenv CLASSPATH=
\usr\files\JWorkPlace> rmiregistry&
Again, try to use the same directory every time you start the RMI registry until you are sure that
your environment is set up properly. Also, avoid starting the registry in the same directory in
which you are about to run your server.
56
Start the RMIActivatableShell Server
Go to the directory where the RMIActivatableShell was developed. Start the
RMIActivatableShell command processor. Again, in this example there is a "service"
directory under example2 that contains a startService script.
D:\chapter3\example2\service>startService
You should verify the contents of the script. Specifically, the three parameters at the end of the
command are for:
•
•
•
The hostname of the RMI registry
The codebase parameter for service startup
The policy file for service activation
This time when you start the service, it will register with the activation system (rmid) and then exit.
When the client invokes the FileList command, the activation system will start the service.
Again, you will see the access to the service-dl.jar file on the HTTP console. If you do
not see this access, then a classpath problem may be the culprit.
List the Registry Contents to Verify Setup
To verify the service, move to the directory and machine where you deployed the server software:
\usr\files\JWorkPlace>java –
Djava.security.policy=/usr/JWorkPlace/policy.all
org.jworkplace.rmi.ListRMIServices
You will see the service listed on the console as
rmi://:1099/RMIActivatableShell.
Test Client
The test client step is exactly as in example1.
Again, you will see the contents of the remote directory displayed on the console.
You no longer have to start the shell manually. It will be started automatically when the first
request to access the service is invoked (see Figure 3.8).
Figure 3.8. Activatable service activation.
57
Congratulations! You now have an environment in place that provides the necessary support for
bootstrapping Jini.
Jini and the RMI Activation Daemon
An RMI activation daemon is needed by several Jini services—for example, the Jini LUS, the
Transaction Manager, and the persistent JavaSpaces Service. An instance of the daemon needs to
be active on each of the machines where Jini services will run. You will see that the RMI
framework significantly impacts and influences the design of Jini services.
When a Jini service starts, it registers with rmid. This registration process is run in its own JVM
spawned by the RMI daemon. At the completion of registration, the process exits. The registration
process consists of passing parameters to rmid that are sufficient to register and start a Jini service
or, as was just demonstrated, any remote service. So, there are two steps:
1. The service will register with rmid in a separate JVM.
2. Activation of the service by rmid will also occur in a separate JVM when requested.
Summary
The Jini reference implementation relies heavily on RMI. So in effect, it inherits all the benefits of
the RMI infrastructure. Although it is not a requirement to use RMI with Jini, you will lose many
of the "out-of-the-box" benefits—a distributed object model supporting code mobility for one.
RMI provides:
58
•
•
•
•
Automatic activation of services and service restoration
Resource conservation by only activating a service when requested
Support for activation groups that share a single JVM
Automatic generation of proxy objects for remote communication
There might be cases where the target platform or device in which a Jini service has been
implemented is resource-constrained and unable to fully participate in an RMI environment. There
are alternative architectures available, such as the surrogate architecture that will be covered later
in the book. For now, we need to demonstrate the role RMI plays in a typical Jini network. With
the background presented on RMI, you are ready to move into the core Jini services, which are the
subjects of the next several chapters.
59
Chapter 4. Building on Jini Foundation Concepts
IN THIS CHAPTER
•
•
Foundational Services
Summary
Foundational Services
The most important concept within the Jini architecture is that of a service. The specification (see
Appendix C, "Links, Community, Resources") defines a service as "an entity that can be used by a
person, a program, or another service. A service might be a computation, storage, a
communication channel to another user, a software filter, a hardware device, or another user."
This is a rather broad definition of what a service may entail. This section will provide an
overview of the foundational services within a Jini network to provide a more concrete
representation of Jini services. Let's start with the services that support the process of service
discovery.
Service Discovery
As was highlighted in Chapter 1, "Introduction to Jini," discovery of services is an important
concept. In peer-to-peer systems, it enables us to discover peers on the network. Web services
promoted service discovery through a Universal Description and Discovery (UDDI) registry. Even
the concepts of the Semantic Web involved software agents being able to dynamically discover
services.
So, it is not surprising that Jini also addresses the discovery of services in a Jini network as
fundamental to enabling the formation of groups and peers. In Jini parlance, these groups or
federations are referred to as a djinn, or Jini community.
Services are found and resolved by a lookup service (LUS). The lookup service is the central
bootstrapping mechanism for the system, and provides the major point of contact between the
system and users of the system. A lookup service maps Java language-defined interfaces
indicating the functionality provided by a service to sets of objects that implement the service.
In contrast to the RMI registry, which uses name-to-object mapping (see Figure 4.1), the Jini
lookup service uses interface-to-object mapping. This proves to be a powerful concept, because
interfaces are inherently extensible.
Figure 4.1. The RMI registry associates a name to an object, as opposed to Jini,
which associates an interface to an object reference.
60
Jini lookup services are organized into groups. You can create groups with any name that you
desire. There is also a default group called the public group. When you start a lookup service, you
specify the groups that the lookup service will support. In addition, when you search for a lookup
service, you specify the groups you require the lookup service to support.
Groups can be organized along departmental boundaries, such as Marketing Department or
Advertising Department, or along other dimensions, such as areas of interest—sports services,
vacation services, and so on. The organization of lookup services is really system- and
application-dependent. Security considerations often influence the configuration.
A service is added to a lookup service, and therefore to a group by a pair of protocols called
discovery and join.
Understanding Discovery Management
The discovery and join protocols are protocols that enable services to discover, become part of,
and advertise supplied services to the other members of the Jini community.
The discovery protocol is used to:
•
•
•
Announce the presence of a lookup service on a local area network (LAN multicast).
Discover one or more lookup services on a local area network (LAN multicast).
Establish communication with a specific lookup service over a wide area network(WAN
unicast).
The discovery protocol requires support for multicast or restricted-scope broadcast (such as UDP),
along with support for reliable unicast delivery in the transport layer (such as TCP). The discovery
protocol makes use of the Java platform's object serialization capabilities to exchange information
in a platform-independent manner.
The join protocol makes use of the discovery protocol to provide a standard sequence of steps that
services should perform when they are starting up and registering themselves with a LUS. The
join protocol is used to register a service and advertise its functionality in all lookup services of
interest.
The discovery protocol supports multicast and unicast messaging.
61
Multicast Versus Unicast Messaging
Multicast is much like radio or TV in the sense that only those who have tuned their receivers—by
selecting a particular frequency—receive the information. You hear the channel you are interested
in, but not the others. The sender sends the information without knowledge of the number of
receivers.
In contrast, when you send a packet and there is only one sender and one recipient, this is referred
to as unicast. TCP is unicast-oriented. On the other hand, UDP supports more paradigms, such as
unicast and multicast. Jini uses both multicast and unicast to support the discovery protocols.
IP addresses are defined using the classification and address ranges seen in Figure 4.2.
Figure 4.2. Multicast addresses are defined in a specific IP address range.
Multicast addresses are in the 224 to 239 range. Jini has reserved the following two multicast
addresses for its use:
•
•
224.0.1.84—jini-announcement
224.0.1.85—jini-request
You can see all the reserved multicast addresses by going to this Web site:
http://www.iana.org/assignments/multicast-addresses
So, how do lookup services bootstrap to the community and use these multicast addresses?
Jini uses the jini-announcement address (224.0.1.84) and the multicast announcement protocol for
lookup services to advertise their existence. When a new lookup service is started, it announces its
availability to potential clients. Also, if a network failure occurs, this protocol can be used by the
lookup service to make clients aware that it is available after the network has been restored.
The multicast announcement protocol follows these steps:
1. Interested entities on the network listen for multicast announcements from lookup
services. If an announcement of interest arrives at such an entity, the entity uses the
unicast discovery protocol to contact the specific lookup service.
62
2. Lookup services take part in the unicast discovery protocol (see Figure 4.3) by sending
multicast announcements of their existence at regular intervals.
Figure 4.3. The multicast announcement protocol provides a "heartbeat"
broadcast for lookup services to advertise their presence.
So, the lookup service can broadcast its existence, like a heartbeat on the network, and interested
clients are listening for heartbeats.
It is assumed that lookup services are relatively stable in the community. In other words, lookup
services should not be entering and leaving the community as frequently as other services or
clients. The lookup service (hereafter referred to as LUS) is required and fundamental to the Jini
community.
So, how do other services that become active broadcast their existence?
Jini uses the jini-request address(224.0.1.85) and the multicast request protocol to discover nearby
lookup services. This is the protocol used by services that become active and need to locate Jini
communities that are within the multicast radius. The multicast radius is roughly the number of
hops beyond which neither requests nor announcements will traverse the network. Network
configurations and hardware devices such as routers control this.
Multicast request datagrams are encoded as a sequence of bytes, using the data and object
serialization facilities of the Java programming language. A multicast discovery request packet
must be 512 bytes in size or less in order to fit into a single UDP datagram.
Table 4.1 illustrates the contents of a multicast request packet body.
Table 4.1. Multicast Request Datagram
Count
(Occurrences)
1
1
1
variable
1
variable
Serialized Type
Description
Protocol version
Int
The port to respond to
Int
The count of lookups
Int
net.jini.core.lookup.ServiceID An array of lookups that
have already responded
The count of groups
Int
java.lang.String
An array of groups of
interest
63
These multicast request datagrams are sent out on the network to find the lookup services that are
within reach.
In Figure 4.4, a service becomes active and broadcasts a request to find nearby LUSes of interest,
for example, groups that it wants to join. Each LUS discovered will return a
MarshalledObject that implements the
net.jini.core.lookup.ServiceRegistrar interface. This will enable the service
to register with the LUS, and in effect, join and advertise its functionality to the Jini community.
Figure 4.4. When a service becomes active, it multicasts a request to find nearby
LUSes.
The multicast protocol is a key to enabling the dynamics of a Jini community. It permits the
community to grow and form dynamically, based on network "closeness" versus traditional
programming lookups or network IP configurations. The using entity does not have a priori
knowledge of the request recipient.
However, this protocol only works on networks that are considered close or nearby. Routers
control the propagation of messages across the network. This is administered through a Time-ToLive (TTL) parameter. Network administrators use this value to control traffic patterns and to
ensure that broadcast storms do not occur.
In Figure 4.5, the Time-To-Live parameter is set to 4 and the multicast request needs to make 5
hops—pass through 5 routers—to make it to the nearest LUS. The LUS will never hear the request,
and the service will never be able to register with that community. However, the unicast discovery
protocol provides an alternative.
Figure 4.5. The TTL parameter constrains the distance that packets can travel over
the network.
64
The unicast discovery protocol is used to communicate with a specific service or LUS. This is
useful for dealing with non-local communities and for using services in specific communities over
a long period of time. This protocol is able to bypass the constraints of the multicast radius.
The unicast discovery protocol works as follows:
1. The LUS listens for incoming connections and, when a connection is made by a client,
responds with a MarshalledObject that implements the
net.jini.core.lookup.Service Registrar interface. This is also what
occurred as a result of the preceding multicast request.
2. An entity that wants to contact a particular LUS uses known host and port information
to establish a connection as seen in Figure 4.6. The client-entity sends a unicast discovery
request and listens for a MarshalledObject in response.
Figure 4.6. A client uses unicast discovery to target a specific LUS.
So, the LUS is crucial to the establishment of a Jini community. The discovery protocol provides
both a dynamic multicast and a directed unicast approach to finding LUSes.
Bootstrapping the Jini Network
Let's return to the GUI-supplied service activation program to bootstrap your Jini network.
The Jini reference implementation provides a LUS named reggie. reggie is an activatable RMI
service. When reggie is started, it will register with rmid and multicast its announcement packets
on the network.
Be sure that rmid is running when you start reggie, or an activation exception will be thrown.
To start reggie, select the Reggie tab and set the following properties, as seen in Figure 4.7:
•
•
•
•
•
Executable jar file—path to the Jini reggie.jar
Codebase—path to the downloadable reggie-dl jar
Security Policy File—path to policy.all file
Log Directory—path to reggie persistent state
Groups—groups this LUS will support
65
Figure 4.7. The Start Service GUI can be used to set the parameters and activate an
HTTP server, rmid, and Jini services.
Select the Run tab and start reggie. reggie will create a log file under the directory specified by the
log directory property. You might want to verify the existence of that file after you have started
the service. If the service fails to start, check the "Gotchas and Common Failures" section in
Appendix A, "Setting Up Your Environment," to troubleshoot the problem.
After you have started reggie, you should be able to start the LookupBrowser, which permits you
to browse the services that are currently running in your Jini community. At this point, the only
service running will be the LUS itself.
To start the LookupBrowser supplied with Jini, select the LookupBrowser tab and set the
following properties, illustrated in Figure 4.8:
•
•
•
•
Executable jar file—path to the Jini examples jar file
Codebase—path to the downloadable examples jar file
Security Policy File—path to the browser policy file
LookupBrowser—com.sun.jini.example.browser.Browser
Figure 4.8. The LookupBrowser can be used to verify and inspect the Jini services
running in your community.
66
You should see a screen that looks like Figure 4.9:
Figure 4.9. The LookupBrowser should display the LUS launched.
The browser verifies that the LUS is running. You can use the browser as an aid in determining
what services are active, how many LUSes are running, attributes of a service, and so on. As we
define services and work through the examples, you might want to start the browser and use it to
look at the different service definitions.
At this point, reggie should be sending announcement packets out on the network. By default,
reggie sends an announcement packet every two minutes. Let's get under the covers of reggie to
verify that multicast packets are being sent.
Building the Multicast Sniffer
67
The multicast sniffer is designed to provide a view into the multicast network, specifically to
monitor the behavior of Jini services. The sniffer defines and creates two threads—an
announcement thread and a request thread.
Listing 4.1 MulticastSniffer Class
import java.net.*;
import java.io.*;
public class MulticastSniffer {
public MulticastSniffer() {
// the announce thread listens on the Jini reserved
multicastannouncement address
// and uses the default port 4160
AnnounceThread athread = new AnnounceThread("224.0.1.84", 4160);
athread.start();
// the request thread listens on the Jini reserved multicast request
address
// and uses the default port 4160
RequestThread rthread = new RequestThread("224.0.1.85", 4160);
rthread.start();
}
public static void main(String args[]) {
MulticastSniffer sniffer = new MulticastSniffer();
}
class AnnounceThread extends Thread {
// The announcement address
InetAddress iaAnnounce = null;
// buffer to hold announcement packet
byte[] buffer = new byte[576];
// A DatagramPacket is used to receive the multicast announcement
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
//
//
//
//
address, and
The
the
The
The
IncomingMulticastAnnouncement class is used to unmarshall
packets sent by the LUS
LUS sends the datagram packets to announce its existence
announcement includes the LUS service ID, the LUS unicast
the groups the
// the LUS supports
IncomingMulticastAnnouncement ima = null;
// The socket used to join the multicast group and receive datagram
packets
MulticastSocket ms = null;
public AnnounceThread(String ip, int port) {
try {
// create the socket and join the multicast group
iaAnnounce = InetAddress.getByName(ip);
ms = new MulticastSocket(port);
ms.joinGroup(iaAnnounce);
}
catch (UnknownHostException e)
68
{
e.printStackTrace();
}
catch (IOException ie) {
ie.printStackTrace(); }
}
public void run() {
System.out.println("Starting multicast announce sniffer");
try {
while(!isInterrupted()) {
dp.setLength(buffer.length);
try {
ms.receive(dp);
} catch (NullPointerException e) { break; }
// Unmarshall the incoming announcement
ima = new IncomingMulticastAnnouncement(dp);
// display the contents to the console
String[] groups = ima.getGroups();
LookupLocator locator = ima.getLocator();
ServiceID id = ima.getServiceID();
Date date = new Date(System.currentTimeMillis());
DateFormat df = DateFormat.getDateTimeInstance();
System.out.println(df.format(date));
System.out.println("Multicast Announcement from ");
System.out.println("Host " + locator.getHost());
System.out.println("Port " + locator.getPort());
System.out.println("Groups: " );
for(int i=0; i<groups.length; i++) {
System.out.println(groups[i]);
}
System.out.println("Service ID " + id);
System.out.println("*************************************");
}
}
catch (SocketException se) { System.err.println(se); }
catch (IOException ie) { System.err.println(ie); }
}
}
class RequestThread extends Thread {
InetAddress iaRequest = null;
byte[] buffer = new byte[576];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
// The IncomingMulticastRequest class is used to unmarshall the packets
// sent by the discovering
// entities to request LUSes
// The request includes the network address of the service,
// the groups the service wants to join and
// the service ID's of LUSes that have already responded
IncomingMulticastRequest imr = null;
MulticastSocket ms = null;
public RequestThread(String ip, int port) {
try {
iaRequest = InetAddress.getByName(ip);
ms = new MulticastSocket(port);
ms.joinGroup(iaRequest);
}
catch (UnknownHostException e) { e.printStackTrace();
69
}
catch (IOException ie) {
ie.printStackTrace(); }
}
public void run() {
System.out.println("Starting multicast request sniffer");
try {
while(!isInterrupted()) {
dp.setLength(buffer.length);
try {
ms.receive(dp);
} catch (NullPointerException e) { break; }
// Unmarshall the incoming request
imr = new IncomingMulticastRequest(dp);
// display the contents to the console
String[] groups = imr.getGroups();
ServiceID[] ids = imr.getServiceIDs();
Date date = new Date(System.currentTimeMillis());
DateFormat df = DateFormat.getDateTimeInstance();
System.out.println(df.format(date));
System.out.println("Multicast request from ");
System.out.println("Host " + imr.getAddress());
System.out.println("Port " + imr.getPort());
System.out.println("Groups: " );
for(int i=0; i<groups.length; i++) {
System.out.println(groups[i]);
}
System.out.println("Service IDs: " );
for(int i=0; i<ids.length; i++) {
System.out.println(ids[i]);
}
System.out.println("*************************************");
}
}
catch (SocketException se) { System.err.println(se); }
catch (IOException ie) { System.err.println(ie); }
}
}
}
To build this example, change to the directory where you copied the book examples. Locate the
Chapter 4 Example 1 directory. The build script requires the location of the Jini distribution to be
set in the environment variable JINI_HOME (see Listing 4.2). Once set, invoke the
build.cmd.
C:\JiniJavaSpaces\Chapter4\example1>build
Listing 4.2 build.cmd Script for Chapter 4 Example 1
@echo off
echo deleting *.class files
del *.class
echo
echo
echo
echo
echo
------------------------------------------Jini and JavaSpaces Application Development
Chapter 4 - Example 1
compiling the MulticastSniffer
-------------------------------------------
70
if not "%JINI_HOME%" == "" goto compile
echo You must set JINI_HOME to point to your Jini installation
echo For example set JINI_HOME=c:\files\jini1_2
goto exit
:compile
javac -d . -classpath %JINI_HOME%\lib\jinicore.jar;%JINI_HOME%\lib\jini-ext.jar;.;
MulticastSniffer.java
:exit
To test the sniffer, enter the following command:
C:\JiniJavaSpaces\Chapter4\example1>startSniffer
Listing 4.3 startSniffer.cmd Script for Chapter 4 Example 1
@echo off
echo
echo
echo
echo
echo
------------------------------------------Jini and JavaSpaces Application Development
Chapter 4 - Example 1
The MulticastSniffer
-------------------------------------------
if not "%JINI_HOME%" == "" goto run
echo You must set JINI_HOME to point to your Jini installation
echo For example set JINI_HOME=c:\files\jini1_2
goto exit
:run
java -cp %JINI_HOME%\lib\jini-ext.jar;.; MulticastSniffer
:exit
You should see an announcement packet sent out every two minutes. You'll display the service ID
that was assigned to the LUS. The service ID is a globally unique identifier (GUID). Every service
in the Jini community, including the LUS, is assigned a unique ID. The unique ID is assigned by
the LUS to all services, and in this case is self-assigned.
Figure 4.10. The MulticastSniffer can be used to display announcement and
request packets being sent by your Jini community.
71
At this point you only see announcement packets to indicate that the Jini network—the LUS—is
available to service requests from client and service entities.
Join and Lookup
Now that you have the LUS in place and have been able to verify the heartbeat of reggie, let's
discuss the join and lookup process. This is the process that enables you to provide and access
services in the Jini community.
As mentioned, the Join protocol makes use of the discovery protocols to provide a standard
sequence of steps that services should perform when they are starting up and registering
themselves with a LUS.
The discovery protocol is used to discover LUSes, and for LUSes to return a proxy object that
implements the ServiceRegistrar interface.
Recall when you built the RMIShell, you had to specify where the RMI registry was running.
If it was not specified, it would default to the local host. In contrast, the discovery process permits
us to dynamically discover where the LUS is running and then allows us to register with it.
Service Registration
After the LUS has been discovered and returns the ServiceRegistrar proxy, you invoke
the register method to register your service. You will do this with all LUSes that are of
interest to you—that is, LUSes that support specific groups.
ServiceRegistration register(ServiceItem item, long leaseDuration)
throws RemoteException;
The register method takes two parameters:
•
•
A ServiceItem
A lease duration
The ServiceItem includes a service ID that must be globally unique and is initially generated
by the LUS as explained previously. In addition, a ServiceItem contains a set of attributes
that are used to augment the service definition and a proxy object used to communicate with the
service.
72
The lease parameter is used to indicate a time-based duration to keep the service active. We will
discuss leasing in general later in this chapter. For now, just realize that every service has a lease
associated with it that controls how long the service will remain active in the LUS. The lease
duration is expressed in milliseconds.
Listing 4.4 ServiceRegistrar Interface
public interface ServiceRegistrar {
ServiceRegistration register(ServiceItem item, long
leaseDuration)
throws RemoteException;
ServiceMatches lookup(ServiceTemplate tmpl, int maxMatches)
throws RemoteException;
int TRANSITION_MATCH_NOMATCH = 1 << 0;
int TRANSITION_NOMATCH_MATCH = 1 << 1;
int TRANSITION_MATCH_MATCH = 1 << 2;
EventRegistration notify(ServiceTemplate tmpl, int
transitions,
RemoteEventListener listener,
MarshalledObject handback,
long leaseDuration) throws RemoteException;
Class[] getEntryClasses(ServiceTemplate tmpl) throws
RemoteException;
Object[] getFieldValues(ServiceTemplate tmpl, int setIndex,
String field)
throws NoSuchFieldException, RemoteException;
Class[] getServiceTypes(ServiceTemplate tmpl, String prefix)
throws RemoteException;
ServiceID getServiceID();
LookupLocator getLocator() throws RemoteException;
String[] getGroups() throws RemoteException;
}
The register method of the ServiceRegistrar interface returns an object that
implements the ServiceRegistration interface. This interface enables the service to
retrieve its service ID and lookup registration and to manage its service attributes. You will need
the service ID to register with other LUSes to ensure your identification is consistent and unique
across the network.
Each implementation of the LUS exports proxy objects that implement the
ServiceRegistration interface.
Listing 4.5 ServiceRegistration Interface
public interface ServiceRegistration {
ServiceID getServiceID();
Lease getLease();
void addAttributes(Entry[] attrSets)
throws UnknownLeaseException, RemoteException;
void modifyAttributes(Entry[] attrSetTemplates, Entry[]
attrSets)
throws UnknownLeaseException, RemoteException;
void setAttributes(Entry[] attrSets)
73
throws UnknownLeaseException, RemoteException;
}
After a service has registered with the LUS, it has joined the community. Clients can now find the
service by performing a lookup on any LUS that the service has registered with.
The JoinManager
Services will often use the net.jini.lookup.JoinManager class to discover and join
a Jini community. This helper class implements the discovery and join protocols, making it easier
for services to manage the "good Jini citizen" process requirements. There is more said about
being a good Jini citizen later in this chapter.
There are five parameters required to construct a JoinManager:
•
•
•
•
•
java.lang.Object is the object/service to be registered. This is the proxy object
that implements the service interface. For example, the Jini LUS registers an object that
implements the ServiceRegistrar interface.
net.jini.core.entry.Entry[] is an array of attributes associated with the
service. Entry objects are used to augment the service definition so users can perform
specific searches for services. For example, an Entry might be used to specify a
location of a service, or a specific vendor's implementation. In that case, only services
matching that criteria would be returned from lookup requests.
net.jini.lookup.ServiceIDListener or
net.jini.core.lookup.ServiceID is the unique service identifier assigned
to the service. If the service has already received the identifier from the LUS, then it uses
the ID as a parameter; otherwise, it supplies an ID listener to receive the ID. The ID
listener stores the ID for subsequent registration(s). Where and how the ID is stored, is
service-dependent. Typically, the service will log this information to safe-storage upon
receipt and retrieve the ID during subsequent starts from a known location.
net.jini.discovery.DiscoveryManagement is an interface that defines
the discovery operations as outlined in the previous section. The LookupDiscovery
class implements this interface. In addition, the LookupLocatorDiscovery class
implements this interface using unicast, rather than the multicast protocol.
net.jini.lease.LeaseRenewalManager is the lease renewal manager
responsible for renewing your lease with the LUS. This is how resources are managed
across the Jini network, based on time allocation management. Leasing is often referred to
as providing the "self-healing" properties of a Jini network.
The JiniShell Example
Let's convert the RMIShell service developed in the last chapter into a Jini Service to
demonstrate the changes required. This will serve to reinforce our discussion and highlight the
extensions Jini provides on top of RMI.
Extend the UnicastRemoteObject to create a non-activatable Jini service:
Listing 4.6 JiniShell Class
// These classes form the basis of the RMI support
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import java.io.IOException;
74
// We will require a number of Jini classes to support our Jini service
import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceID;
import net.jini.discovery.DiscoveryGroupManagement;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lookup.JoinManager;
import net.jini.lookup.ServiceIDListener;
public class JiniShell extends UnicastRemoteObject
implements Shell
{
// The LookupDiscoveryManager you are using to find (request) LUSes
private LookupDiscoveryManager lookupDiscMgr;
// The lookup locator array that contains specific LUSs
private LookupLocator locators[];
// The groups array that contains specific groups, no groups, or all groups
private String groups[] = DiscoveryGroupManagement.ALL_GROUPS;
// The manager for joining LUSs
private JoinManager joiner = null;
// ServiceID returned from the lookup registration process
private ServiceID serviceID = null;
Add an init method to your constructor to do initialization specific to Jini.
public JiniShell() throws IOException {
// call the UnicastRemoteObject to export the object
super();
// Initialize the Jini service
init();
}
Create a discovery manager by passing parameters to discover all LUSes within the multicast
radius. Do this by specifying ALL_GROUPS and null parameters:
private void init() throws IOException {
try {
lookupDiscMgr = new LookupDiscoveryManager(groups, locators, null);
} catch (IOException e) {
System.err.println("Problem starting discovery");
e.printStackTrace();
throw new IOException("Problem starting discovery:" +
e.getLocalizedMessage());
}
/* Register this service with
if (serviceID == null) {
joiner = new JoinManager(
this,
null,
new SrvcIDListener(),
lookupDiscMgr,
null);
} else {
joiner = new JoinManager(
this,
any configured LUSes */
// First instance ... need service id
// service object
// service attributes – none at this time
// ServiceIDListener - internal helper class
// DiscoveryManagement - default
// LeaseRenewalManager - default
// Rejoin with (recovered) state information
// service object
75
null,
serviceID,
lookupDiscMgr,
null);
//
//
//
//
service attributes – none at this time
Service ID – already have an ID
DiscoveryManagement - default
LeaseRenewalManager - default
}
}
// implementation of the Shell interface
public Object executeCommand(Command cmd) {
return cmd.execute();
}
The main method does not differ significantly from prior examples; however, you no longer
need to bind to the RMI registry because you now will use the LUS (ServiceRegistrar) to
return a reference to the remote object:
public static void main(String[] args) {
// set the security manager
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
Shell shell = new JiniShell();
System.out.println("JiniShell bound");
Thread.currentThread().join();
} catch (Exception e) {
System.err.println("JiniShell exception: " +
e.printStackTrace();
}
e.getMessage());
}
// Utility method for setting the service's ID obtained from
// the lookup registration process.
private void setServiceID(ServiceID id) {
serviceID = id;
}
The SrvcIDListener class handles the callback of the service ID assignment from the
JoinManager. You will continue to evolve this process to persist the service ID to a log file:
private class SrvcIDListener implements ServiceIDListener
{
public SrvcIDListener() {
super();
}
/**
* The JoinManager will invoke this method when it receives a
* valid ServiceID from a LUS.
*/
public void serviceIDNotify(ServiceID id) {
// Set the ID
setServiceID(id);
System.out.println("Received service id");
// Log this event
}
}
}
76
There are a number of important aspects of being a Jini service that have been omitted for the
moment. The preceding service only demonstrates the core requirements to registration. For
example, you did not persist your service ID, so restarts of the service would pass a listener, and
thus the LUS would generate a new unique ID as opposed to reusing the existing ID. Any client
who had saved the original ID would not be able to find the service again. The shortcomings of
this example will be corrected as additional Jini concepts are introduced to the service framework.
Building a Simple Client
Let's now build a simple client to test the service.
Listing 4.7 JiniCommandLine Class
import java.rmi.*;
import java.io.IOException;
import java.util.Vector;
// Jini classes used to find and bind to a Jini service
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryManagement;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceRegistrar;
import org.jworkplace.command.*;
public class JiniCommandLine {
public JiniCommandLine() { }
// method to find a service given a service template
private Object lookup(ServiceTemplate template) throws IOException
{
// Your internal class ServiceListener does the actual work
ServiceListener ss = new ServiceListener(template);
return ss.lookup();
}
public static void main(String args[]) {
// Set a default directory to search
String directory = ".";
if(args.length == 1) {
directory = args[0];
}
// set security manager if not set
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
JiniCommandLine cmdLine = new JiniCommandLine();
// create a service template to represent the required service
ServiceTemplate template;
// We pass a class that identifies an interface rather than a name
Class[] types = {
Class.forName("Shell") } ;
// We do not constrain our service search other than interface type
// null indicates match anything on service ID and attributes
77
template = new ServiceTemplate(null, types, null);
}
// call the lookup passing our template
Shell shell = (Shell)cmdLine.lookup(template);
FileList command = new FileList(directory);
String[] files = (String[]) (shell.executeCommand(command));
if(files == null) {
System.out.println("Invalid directory");
} else {
for(int i=0; i<files.length; i++)
System.out.println(files[i]);
}
catch (Exception e) {
System.err.println("FileList exception: " +
e.getMessage());
e.printStackTrace();
}
}
You create a ServiceTemplate to indicate the service you want your discovery process to
find. A ServiceTemplate is used to match service items that are registered in the LUS. The
template contains the service ID if known, an array of java.lang.Class objects of
supported interfaces, and the attributes to limit the returned services. Null values are supplied for
the service ID and attribute parameters, which instructs the LUS to match anything. You ask for
any object that implements the Shell interface by setting the java.lang.Class array
parameter to your Shell class name.
Your ServiceListener implements the DiscoveryListener interface. The internal
class ServiceListener is a helper class for your client. It is passed the template that your
client wants to find. The DiscoveryListener interface defines two methods,
discovered and discarded, as defined in Listing 4.8.
Listing 4.8 DiscoveryListener Interface
public interface DiscoveryListener extends java.util.EventListener
// Called when one or more LUS registrars has been discarded.
void discarded(DiscoveryEvent e);
// Called when one or more LUS registrars has been
// discovered.
void discovered(DiscoveryEvent e);
}
Inner class of the Jini command line:
private class ServiceListener implements DiscoveryListener {
// Our discovery management support
private DiscoveryManagement mgt;
// A vector to hold all services discovered
private Vector services = new Vector();
// A template used to indicate the service requested by the
client
private ServiceTemplate template;
public ServiceListener(ServiceTemplate template) throws
IOException {
super();
78
this.template = template;
// sequence to ensure all lookups are heard
mgt = new LookupDiscovery(LookupDiscovery.NO_GROUPS);
// we implement the listener interface
mgt.addDiscoveryListener(this);
// now set all groups
((LookupDiscovery)mgt).setGroups(LookupDiscovery.ALL_GROUPS);
}
// client will wait until matching service is discovered
public synchronized Object lookup() {
while(services.size() == 0) {
try {
wait();
} catch (InterruptedException ex) {
}
}
// just return the first match found
return ((ServiceItem)services.elementAt(0)).service;
}
The discovered method receives a DiscoveryEvent, which is passed by the
LookupDiscovery object when it discovers one or more LUSes. The getRegistrars
method of the DiscoveryEvent returns an array containing the ServiceRegistrar
for each newly discovered LUS.
Display the discovered LUSes to the console to demonstrate the type of information available:
public synchronized void discovered(DiscoveryEvent de) {
System.out.println("Discovered LUS: ");
ServiceRegistrar[] registrars = de.getRegistrars();
for (int i=0; i < registrars.length; i++) {
try {
System.out.println("URL:
" + registrars[i].getLocator(). toString());
System.out.println("ID:
" + registrars[i].getServiceID());
String groups[] = registrars[i].getGroups();
// simply display the first group returned
System.out.println("GROUPS: " + groups[0]);
The lookup method of the ServiceRegistrar is used to return an array of
ServiceMatches objects. The service template containing the service Shell interface is
used as a parameter to request that only services implementing the Shell interface be returned.
The ServiceMatches object returned by the LUS will contain an array of ServiceItem
objects and a count of the number returned. The ServiceItem is added to Vector of
services, which in this simple example is used to trigger the notification to the suspended client
that an object implementing the Shell interface has been found:
ServiceMatches sm = registrars[i].lookup(template, Integer. MAX_VALUE);
System.out.println("Matching services found ------: " + sm.
totalMatches);
System.out.println("");
for(int j=0; j < sm.items.length; j++) {
// Process each ServiceItem
79
if(sm.items[j].service != null) {
services.addElement(sm.items[j]);
} else {
System.out.println("Service item null" + sm.items[j]. service);
}
}
}
// notify the client a matching service has been found
System.out.println("Notifying...");
notifyAll();
catch(Exception e) { e.printStackTrace(); }
}
}
// we ignore discarded LUS objects in this example
public void discarded(DiscoveryEvent de) { }
}
}
The following code fragment in main is executed upon notification; it is the familiar
FileList command. The results are displayed to the console:
Shell shell = (Shell)cmdLine.lookup(template);
FileList command = new FileList(directory);
String[] files = (String[]) (shell.executeCommand(command));
Running the Example
To begin, ensure that you have HTTP servers running on the client and server. In addition, rmid
and reggie should be running on the server.
Now it is time to build and test your JiniShell Service. The compilation and stub generation
process should be familiar to you now. You just need to ensure you add the necessary Jini jar files
to the process.
For compilation, this usually amounts to adding the jini-core.jar file and the jini-
ext.jar file to the classpath.
Compile the Files
Change to the directory where you copied the examples for this book.
For example, in C:>cd c:\JiniJavaSpaces\Chapter4\example2, you should
see the following files:
JiniCommandLine.java
FileList.java
Command.java
Shell.java
JiniShell.java
80
Invoke the java compiler using the build script provided:
C:\JiniJavaSpaces\Chapter4\example2>build
Listing 4.9 build.cmd Script File
@echo off
echo deleting *.class files
del *.class
echo
echo
echo
echo
echo
------------------------------------------Jini and JavaSpaces Application Development
Chapter 4 - Example 2
compiling
-------------------------------------------
if not "%JINI_HOME%" == "" goto staticClasspath
echo You must set JINI_HOME to point to your Jini installation
echo For example set JINI_HOME=c:\files\jini1_2
goto exit
echo compiling files
:staticClasspath
echo Setting your CLASSPATH statically.
set CP=
if exist "%JINI_HOME%\lib\jini-core.jar" set
CP=%CP%;%JINI_HOME%\lib\jini-core.jar
if exist "%JINI_HOME%\lib\jini-ext.jar" set
CP=%CP%;%JINI_HOME%\lib\jini-ext.jar
if exist "%JINI_HOME%\lib\sun-util.jar" set
CP=%CP%;%JINI_HOME%\lib\sun-util.jar
echo Using CLASSPATH: %CP%
javac -d . -classpath %CP%;.; Command.java
javac -d . -classpath %CP%;.; JiniCommandLine.java
javac -d . -classpath %CP%;.; FileList.java
javac -d . -classpath %CP%;.; Shell.java
javac -d . -classpath %CP%;.; JiniShell.java
echo generating the JiniShell_Stub class
rmic -v1.2 -classpath .\
-d . JiniShell
echo Creating the service download jar file
jar -cf service-dl.jar JiniShell_Stub.class Command.class Shell.class
echo Creating the service jar file
jar -cmf service.txt service\service.jar JiniShell.class
JiniShell$SrvcIDListener .class
JiniShell_Stub.class Command.class Shell.class
echo Creating the client download jar file
jar -cf client-dl.jar Command.class Shell.class FileList.class
echo Creating the client jar file
jar -cmf client.txt client\client.jar JiniCommandLine.class
81
JiniCommandLine$ServiceListener.class
Shell.class
FileList.class Command.class
:exit
Create the Required Stub Interfaces
This will compile the files and invoke the rmic compiler to generate the stub file for the
JiniShell class.
Create Jar Files (Client-Side and Server-Side)
It also creates the client-side and server-side jar files.
Copy the client-dl.jar file to the client HTTP directory.
Copy the service-dl.jar file to the server HTTP directory.
Define the Security Policy Files for Client and Server
Create the policy.all file for both the server and the client or use the one provided with the
Jini distribution:
grant {
permission java.security.AllPermission "", "";
} ;
Start the JiniShell Server
Go to the service directory. Start the JiniShell command processor by invoking the service
script.
C:\JiniJavaSpaces\Chapter4\example2\service>startService
Listing 4.10 startService.cmd Script File
@echo off
if not "%HTTP_ADDRESS%" == "" goto gotServerHTTP
echo You must set HTTP_ADDRESS to point at your service HTTP server
echo For example set HTTP_ADDRESS=[hostname][:port]
goto exit
:gotServerHTTP
echo Setting your CLASSPATH statically.
set CP=
if exist "%JINI_HOME%\lib\jini-core.jar" set
CP=%CP%;%JINI_HOME%\lib\jini-core.jar
if exist "%JINI_HOME%\lib\jini-ext.jar" set
CP=%CP%;%JINI_HOME%\lib\jini-ext.jar
echo Using CLASSPATH: %CP%
java -Djava.security.policy=%JINI_HOME%\policy\policy.all
82
-Djava.rmi.server.codebase=http://%HTTP_ADDRESS%/service-dl.jar -cp
%CP%;..\; JiniShell
:exit
Test Client
To test the client, move to the directory and machine where you deployed the client software. Run
the following client command:
C:\JiniJavaSpaces\Chapter4\example2\client>startClient
Listing 4.11 startClient.cmd Script File
@echo off
if not "%HTTP_ADDRESS%" == "" goto gotClientHTTP
echo You must set HTTP_ADDRESS to point at your service HTTP server
echo For example set HTTP_ADDRESS=[hostname][:port]
goto exit
:gotClientHTTP
java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all
-Djava.rmi.server.codebase=http://%HTTP_ADDRESS%/client-dl.jar
client.jar %1
:exit
Where %1 is the remote directory to search and display files.
Again, you should see the contents of the remote directory displayed on the console.
Your Shell interface is now a Jini service, and is available on the Jini network. Any client
within the multicast radius can discover the service using multicast discovery.
In addition, with a minor change, such as using a specific LookupLocator address, a remote
client over a wide area network or the Internet can also interface with the command service.
You can change the discovery process to use unicast discovery by changing from
LookupDiscovery to LookupLocatorDiscovery.
Instead of specifying groups, specify an array of URLs of the form
jini://hostname/port/ where:
•
•
•
The protocol is jini;
The hostname defines the name of the host on which to find the LUS;
The network port is optional and defaults to 4160.
The LookupLocatorDiscovery class is used to participate in unicast discovery. The class
takes an array of locators to find specific LUSes.
Add a LookupLocator array to your class definition:
// The lookup locator array that contains specific LUSs
83
private LookupLocator locators[];
public ServiceListener(ServiceTemplate template) throws IOException
{
super();
this.template = template;
// change to LookupLocatorDiscovery
mgt = new LookupLocatorDiscovery(locators);
// you still implement the listener interface
mgt.addDiscoveryListener(this);
// now set specific locators
locators[0] = new LookupLocator("jini://hostname:port/");
((LookupDiscovery)mgt).setLocators(locators);
}
So, you have just demonstrated three different approaches to the discovery process:
•
•
•
The server used the LookupDiscoveryManager class, which combines unicast
and multicast discovery
The first client example used multicast by using the LookupDiscovery class
The modifications outlined use unicast discovery by creating an instance of
LookupLocatorDiscovery
The discovery technique you use will depend on the security and visibility requirements of your
service. Security is discussed in depth in Chapter 9"Security in Jini.",
Service Attributes
The Jini LUS provides a framework that supports service discovery by interface type. In addition,
LUSes support service attributes. Service attributes are used to further define the service and
permit using entities to refine their discovery parameters.
In the JiniShell example, you used the JoinManager class to help with the discovery
and join process.
JoinManager joiner = new JoinManager(
this,
// service object
null,
// service attributes – none at this
time
new SrvcIDListener(),
// ServiceIDListener - internal helper
class
lookupDiscMgr,
// DiscoveryManagement - default
null);
// LeaseRenewalManager - default
The second parameter to creating a JoinManager is an array of
net.jini.core.entry.Entry attributes. This class is the supertype of all entries that
can be stored in a Jini LUS. It is an interface that serves as a marker interface—there are no
methods defined. An object that implements this interface must adhere to the Entry
specification. It must have a default, no argument constructor, and should contain only public
instance variables. All instance variables must be serializable. Each field is serialized separately,
so references between two fields of an entry will not be reconstituted to be shared references, but
instead to separate copies of the original object.
84
AbstractEntry is a basic implementation of the Entry interface. This class is often used
as the base class for defining service attributes, because it adheres to the Entry specification.
The Pre-Defined Attribute Definitions
Jini supplies the following pre-defined attributes:
•
•
Address— An Entry object used to represent an address. This address is used to
represent a mailing address; for example, it contains a postalCode defined as a
String.
Comment— An Entry object that contains a String to attach a comment to a
service.
•
Location— An Entry object that refines the address attribute to contain the floor or
•
Name— An Entry object that represents a name for the service. This is often used to
room in a building.
•
•
•
display a readable value for the service.
ServiceInfo— An Entry object that contains implementation details, such as
vendor, manufacturer, version, and model of the service.
ServiceType— An Entry object that provides service information to a human user,
such as service name, an icon representation, and a description.
Status— An Entry object used to represent the status of a service.
In addition the net.jini.lookup.entry package contains a matching bean definition for
each entry type. For example, Address has an associated AddressBean. The bean class has
the necessary get and set methods to manipulate the content of the entry definition.
EntryBeans is an Entry object that defines a utility class for handling JavaBeans
components that relate to Entry classes in the LUS.
Developing a Taxonomy of Attributes
You can use Entry objects to augment service definitions. It is possible to use these object
definitions to create ordered groups or categories. For example, you can use Address objects to
classify according to geographic relationships, or Location objects to partition a building or
campus.
It is also possible to create new Entry object definitions that represent information taxonomies
or hierarchical categories similar to those found on Web portals. For example, a health portal can
offer a Jini hospital referral service that recommends hospitals based on an attribute such as
"cardiac care" or "respiratory disease" treatment.
Searching, Matching, and Filtering Services Based on Attributes
You can easily create Entry object definitions. For example, to augment the JiniShell
service, you can define the following Entry objects:
private Entry[] entries = {
new Name("JiniShell"), // providen lookup by name capabilities
new ServiceInfo("JWorkPlace",
//provide specific service information
"Robert Flenner",
"Sams",
"1.0","", "")
};
85
The new JoinManager construction would simply change to:
JoinManager joiner = new JoinManager(
this,
// service object
entries,
// service attributes
new SrvcIDListener(),
// ServiceIDListener - internal helper
class
lookupDiscMgr,
// DiscoveryManagement - default
null);
// LeaseRenewalManager - default
To find a specific service, you can change your template definition:
// create a service template to represent the required service
ServiceTemplate template;
// pass a class that identifies an interface rather than a name
Class[] types =
{
Class.forName("org.jworkplace.command.Shell") } ;
// Create the entries to match on
private Entry[] entries = {
new Name("JiniShell"),
// provide lookup by name
capabilities
new ServiceInfo("JWorkPlace",
// provide specific service
information
"Robert Flenner",
"Sams",
"1.0","", "") } ;
// Construct the template using the Entry objects
template = new ServiceTemplate(null, types, entries);
Then, when you pass the template to the LUS, only those services matching the template will be
returned:
ServiceMatches sm = registrars[i].lookup(template, Integer.MAX_VALUE);
Entry objects provide a powerful mechanism to service discovery. You will continue to work
through service attribute definitions as you develop Jini applications.
Distributed Events
Jini supports a distributed event model to enable loosely-coupled notifications from remote
sources. The net.jini.core.event.RemoteEvent class is the superclass of all
remote events in the Jini framework.
The state contained in a RemoteEvent object includes:
•
•
•
•
A reference to the object in which the event occurred
An ID that indicates the kind of event relative to the object in which the event occurred
A long, which indicates the sequence number of this instance of the event kind
A MarshalledObject that is handed back to the client when the notification occurs
The combination of the event identifier and the object reference obtained from the
RemoteEvent object should uniquely identify the event type.
86
The sequence number obtained from the RemoteEvent object is an increasing value that can
act as a hint to the number of occurrences of this event relative to some earlier sequence number.
Any object that generates a RemoteEvent must ensure a unique sequence number is assigned
to each event it generates.
Notifications
The ServiceRegistrar interface implemented by the LUS contains a notify method:
EventRegistration notify(ServiceTemplate tmpl,
int transitions,
RemoteEventListener listener,
MarshalledObject handback,
long leaseDuration) throws RemoteException;
Applications can use this notify method by passing a RemoteEventListener object to
request notification when registered services change. ServiceEvent objects are returned to
the listener when services in the Jini network change. The applications specify the events of
interest by specifying a service template and a transition parameter.
•
•
•
TRANSITION_MATCH_NOMATCH— A service item that previously matched the
service template no longer matches the template.
TRANSITION_NOMATCH_MATCH— A service item was added that matches the
template or was changed to match the template.
TRANSITION_MATCH_MATCH— A service item changed but still matches the
template.
A ServiceEvent extends the RemoteEvent object and includes the service ID, the
service item, and the type of transition that took place. This class is used for remote events sent by
the LUS. Sequence numbers for a given event ID are strictly increasing. If there is no gap between
two sequence numbers, no events have been missed; if there is a gap, events might (but might not)
have been missed. For example, a gap might occur if the LUS crashes, even if no events are lost
because of the crash.
The net.jini.core.event.RemoteEventListener is an interface that defines
the notify method. The RemoteEventListener interface is implemented by any object
that wants to receive a notification of a remote event from some other object.
public void notify(RemoteEvent theEvent)
throws UnknownEventException,
java.rmi.RemoteException
The call to notify is synchronous to enable the party making the call to know whether the call
succeeded. However, the recipient of the call should return from the notify method as quickly
as possible. UnknownEventException is thrown when the recipient does not recognize the
combination of the event identifier and the event source as something in which it is interested.
Throwing this exception has the effect of asking the sender not to send further notifications of this
kind of event from this source in the future.
A MarshalledObject can be supplied to an event registration and will be returned to the
RemoteEventListener during notification. The listener can retrieve the
MarshalledObject by invoking the getRegistrationObject method of the
RemoteEvent.
87
The Self-Healing Network
If you've integrated systems across a network, you know that the resiliency of the network and the
capability to cope with network failures separates good applications from primitive applications.
Jini assumes at its foundation that the network might be unstable.
Jini introduces the idea of a lease to service management. If you are a user of a service, you never
own the service, you simply lease it—nothing is permanent. All resources must have some
management process that periodically checks to see whether everything is OK, and to determine
whether the resource is still required by the system or user.
Imagine that I am a service provider publishing services offered to B2B exchanges. My system
crashes or I have system problems. How does the exchange maintain my status in a timely fashion?
There's not likely to be a consistent approach across exchanges. However, leasing can provide
consistency and time-based resource management flexibility.
Leasing is an important part of Jini infrastructure robustness. Leasing permits the LUS to delete
services that have failed to renew their lease. If you provide or are using a service, you must
periodically renew your lease. The renewal period depends on the application. You request a finite
renewal period on your lease, and the service provider determines the actual lease commitment.
The service provider is in the best position to determine resource availability. The provider, if
designed correctly, can become more intelligent about resource commitments than most software
is today.
It's analogous to apartment leasing. I can ask the apartment complex for a 6-month lease. They
inform me they can only commit to a 1-month lease period. After one month, I must renew my
lease, or I am evicted!
When you use the Web, you often find invalid URL references or slow sites. Web services are
being built on this same infrastructure and architecture. System and network instability will be an
issue. Web services will require the self-healing properties of a Jini network.
Leasing
When a service registers with the LUS, it receives a lease from the LUS. More specifically, the
ServiceRegistrar interface returns a ServiceRegistration object when the
register method is invoked. The ServiceRegistration contains a getLease
method that returns a Lease object.
The Lease interface defines a type of object that is returned to the leaseholder and issued by the
lease grantor. Particular instances of the Lease type will be created by the grantors of a lease
and returned to the holder of the lease as part of the return value from a call that allocates a leased
resource, such as the register method. The call that requests a leased resource will typically
include a requested duration for the lease. If the request is for a particular duration, the lease
grantor is required to grant a lease of no more than the requested period of time. A lease can also
be granted for a period of time shorter than that requested.
It is the responsibility of the service to ensure that the lease does not expire. In order for a service
to maintain its residency in the LUSes it has joined, the service must provide for the coordination,
systematic renewal, and overall management of all leases on that residency.
If the service fails to renew the lease when it expires, the LUS will remove the service from its list
of available services. The LUS assumes the service is having a problem that keeps it from
performing satisfactorily. This is why the Jini network is often referred to as "self-healing." It
proactively grooms the service environment without the need for human intervention.
88
The net.jini.lease package introduced with Jini 1.1 contains the classes related to lease
management.
The LeaseRenewalManager is a utility class that can be used to ease the development
required to manage leases. The class creates a thread that sleeps until it is time to renew the leases
under its management. The LeaseRenewalManager is active as long as the virtual machine
that activated the manager is active.
The LeaseRenewalService is a Jini service that enables leases to be managed in a
separate virtual machine process. We will discuss the LeaseRenewalService in greater
detail when we cover helper services in Chapter 7, "The Helper Services."
Recall that we have already used a LeaseRenewalManager (LRM), although we did not
explicitly provide one when you instantiated a JoinManager.
JoinManager
joiner = new JoinManager(
this,
// service object
entries,
// service attributes
new SrvcIDListener(),
// ServiceIDListener - internal helper
class
lookupDiscMgr,
// DiscoveryManagement - default
null);
// LeaseRenewalManager - default
The last parameter to the JoinManager takes a renewal manager. If one is not supplied, the
JoinManager instantiates one on your behalf. You can gain access to the LRM through the
JoinManager's getLeaseRenewalManager method.
public LeaseRenewalManager getLeaseRenewalManager()
Clients of the LRM simply give their leases to the manager, and the manager renews each lease as
necessary. Failures encountered while renewing a lease can optionally be reflected to the client via
LeaseRenewalEvents by providing a LeaseListener.
Entities can also explicitly create an instance of this class in their own virtual machine to locally
manage the leases granted to them.
public class LeaseRenewalManager
LeaseRenewalManager()
LeaseRenewalManager(Lease lease, long desiredExpiration,
LeaseListener listener)
public void cancel(Lease lease)
public void clear()
public long getExpiration(Lease lease)
public void remove(Lease lease)
public void renewFor(Lease lease, long desiredDuration,
LeaseListener listener)
public void renewFor(Lease lease, long desiredDuration, long
renewDuration,
LeaseListener listener)
public void renewUntil(Lease lease, long desiredExpiration,
LeaseListener listener)
public void renewUntil(Lease lease, long desiredExpiration,
long renewDuration, LeaseListener listener)
public void setExpiration(Lease lease, long expiration)
}
89
Time-Based Resource Allocation
The LeaseRenewalManager distinguishes between two time values associated with lease
expiration: the desired expiration time for the lease, and the actual expiration time granted when
the lease is created or was last renewed. The desired expiration represents when the client would
like the lease to expire. The actual expiration represents when the lease is going to expire if it is
not renewed. Both time values are absolute times, not relative time periods.
Each lease in the managed set also has two other associated attributes: a desired renewal duration,
and a remaining desired duration. The desired renewal duration is specified (directly or indirectly)
when the lease is added to the set. This duration must normally be a positive number; however, it
might be Lease.ANY if the lease's desired expiration is Lease.FOREVER. The remaining
desired duration is always the desired expiration minus the current time.
The methods of this class are appropriately synchronized for concurrent operation. Additionally,
this class makes certain guarantees with respect to concurrency. When this class makes a remote
call (for example, when requesting the renewal of a lease), any invocations made on the methods
of this class will not be blocked. Similarly, this class makes a re-entrance guarantee with respect to
the listener objects registered with this class. Should this class invoke a method on a registered
listener (a local call), calls from that method to any other method of this class are guaranteed not
to result in a deadlock condition.
Summary
The most important concept within the Jini architecture is that of a service.
Services are found and resolved by a lookup service (LUS). The LUS is the central bootstrapping
mechanism for the system and provides the major point of contact between the system and users
of the system.
The service discovery protocols require support for multicast or restricted-scope broadcast, along
with support for reliable unicast delivery in the transport layer. The discovery protocols make use
of the Java platform's object serialization capabilities to exchange information in a platformindependent manner.
The join and lookup process enables us to provide and access services in the Jini community. The
Join protocol makes use of the discovery protocols to provide a standard sequence of steps that
services should perform when they are starting up and registering themselves with a LUS.
The Jini LUS provides a framework that supports service discovery by interface type. In addition,
LUSes support service attributes. Service attributes are used to further define the service and allow
using entities to refine their discovery parameters.
Jini supports a distributed event model to enable loosely coupled notifications from remote
sources. The distributed event model is simple to implement with a single notify method
defined. Event frameworks can be quickly assembled to provide support for remote notification,
filtering, and message delivery.
Leasing is an important part of Jini infrastructure robustness. Services must maintain their leases
in all registered LUSes. The LUS will remove any service that does not renew its lease in a timely
manner. This is why the Jini network is often referred to as "self-healing." The LUS proactively
grooms the service environment without the need for human intervention.
90
Part II: Designing Applications Using the Jini
Framework
IN THIS PART
5 JavaSpaces Service
6 Transaction Service
7 The Helper Services
8 Service Administration
9 Security in Jini
91
Chapter 5. JavaSpaces Service
IN THIS CHAPTER
•
•
•
•
•
•
•
•
•
The JavaSpaces Service
The Architecture of JavaSpaces
Entries Revisited
The Simple API
Distributed Data Structures
Application Basics
Deploying JavaSpaces
A Sample Application—Building the MessagePad
Summary
This chapter provides a brief overview of the JavaSpaces service. It expands the notion of entries
defining service attributes and entries defining models of communication and control in
JavaSpaces, such as messaging systems and workflow control systems.
The JavaSpaces Service
JavaSpaces is a Jini service that supports distributed persistence and the design of distributed
algorithms. JavaSpaces is closely tied to the Jini architecture; in fact, it is one of the few services
that comes bundled with Jini.
JavaSpaces was heavily influenced by the concept of the tuple space, first described in 1982 in a
programming language called Linda that was developed at Yale University. Linda was designed as
a coordination language for ensemble (distributed and parallel) computing. Like Linda,
JavaSpaces is designed to significantly ease the development of distributed and parallel processing
systems.
The basic idea of ensemble programming is that you can have many active programs distributed
over physically dispersed machines, unaware of each other's existence, that are still able to
communicate. They communicate to each other by releasing data (a tuple) into tuple space.
Programs read, write, and take tuples that are of interest to them from the tuple space.
JavaSpaces, however, extends this model of communication by defining the data as objects. So,
rather than simply passing data between processes, you are able to pass objects, and thus behavior.
JavaSpaces uses RMI and object serialization from the Java programming language to provide
these features. In addition, JavaSpaces leverages the Jini infrastructure that includes distributed
events, leasing, and lightweight transactions.
The Architecture of JavaSpaces
A JavaSpaces server is used to mediate the communications between networked systems. It
provides much of the common functionality required for distributed systems, which simplifies the
development task, particularly when you can model your task as a flow of objects between
systems distributed over a network as seen in Figure 5.1.
92
Figure 5.1. The JavaSpace API provides a simple programming interface for
distributed processes. It allows processes to read and write objects and to notify
processes when an object of interest arrives in the space.
With JavaSpaces, distributed processes communicate by reading and writing entries into space.
The JavaSpace API provides an event notification mechanism, which enables processes to register
for notification when a specific object is written to space. A space is defined by JavaSpaces as a
shared, network-accessible repository for objects. This shared repository can persist objects
written to a space beyond the lifetime of the process that created them. Spaces provide reliable
storage for objects and support leases.
Objects in a space are located via associative lookup, as opposed to more traditional keys or
identifiers. More is said about associative lookup when entries are discussed later in this chapter.
When you read or take an object from a space, a local copy of the object is created. As with any
other local object, you can modify its public fields and invoke its methods.
In addition, JavaSpaces supports transactions for single operations, such as writing an object to a
single space, as well as multiple operations over one or more spaces.
JavaSpaces Versus Traditional Database Technology
JavaSpaces is not a database, although it does have database characteristics. For instance, the
capability to persist objects would lead to comparing it to object database technology. However,
you will find that the API to JavaSpaces is so simple that it could not possibly cover all the
functions and features normally associated with databases.
The differences include the following:
•
No defined query language—objects are retrieved with templates, not a specifically
designed query language such as SQL or OQL.
93
•
•
No transparent access provided to objects—rather than blurring the distinction between
in-memory and storage access of objects, JavaSpaces works on serialized copies of entries.
A general lack of database tools and utilities—JavaSpaces provides a simple API and
defines a minimal and lightweight persistence engine.
JavaSpaces' strength lies in its capability to simplify the programming required to coordinate
distributed communication and parallel processing.
JavaSpaces Versus MOM (Message Oriented Middleware)
There might also be a tendency to compare JavaSpaces to message oriented middleware (MOM).
Again, the comparisons are justified, but miss the mark.
MOM products support two fundamental models of communication; point-to-point and publishsubscribe.
Point-to-Point
In the point-to-point model, requests are targeted to a specific queue or receiver. Queues are
named as network-accessible resources. A client sends a request to a specific receiver. The
receiver then reads the message from the queue, which is managed by the messaging middleware.
The messaging can be asynchronous or synchronous. Asynchronous messaging permits the
initiating process to continue processing and avoid blocking until the message is retrieved.
Publish-Subscribe
In the publish-subscribe model, requests are targeted to a queue that is accessible by one or more
receivers. The requestor does not know the identity of the receiver as in the point-to-point model.
Subscribers register interest in a specific queue, and the messaging software ensures that messages
are delivered to the subscribers.
Both models have become popular because of their capability to loosely couple existing systems.
Although JavaSpaces also provides loose coupling for distributed processes and MOM models of
communication, there are a number of important differences.
The differences include the following:
•
•
•
•
•
MOM supports messages; JavaSpaces supports objects. Most messaging products support
the exchange of data. JavaSpaces supports the exchange of objects, and therefore active
processes or behavior.
MOM products often involve extensive administrative overhead. JavaSpaces are simple to
set up and administer.
Messaging products have proprietary interfaces, which means that two different
messaging products might not be able to communicate without some gateway or bridging
technology.
JavaSpaces supports persistent messaging. A JavaSpaces message is an object in its own
right, and can persist and manage its lifecycle independent of the originating process.
JavaSpaces supports transactions and leases inherently. The concept of leases has no
direct counterpart in MOM products.
Although comparisons to database technology and message oriented middleware are valid,
JavaSpaces still provides significant differences. These differences point to a new environment
supporting networked applications as the norm, rather than the exception.
94
Entries Revisited
Entries were introduced when templates built to define and find services were discussed in
Chapter 4. Recall that when you register a service, you can associate attributes with the service,
such as address- or service-specific information. These attributes were defined by the
net.jini.core.entry.Entry interface.
package net.jini.core.entry;
public interface Entry extends java.io.Serializable {
}
The net.jini.core.entry.Entry interface is also used to identify entries that are
capable of being used in JavaSpaces. You will implement this interface or extend the
AbstractEntry class to create objects for use in a space.
Let's review the rules associated with Entry objects:
•
•
•
It must have a default no argument constructor, which is required for serialization.
It should contain only public instance variables, which are required for associative
lookups.
All instance variables must be serializable so that no primitive types are written or read
from space.
In addition, each public field is serialized separately. So, two fields referencing the same object
prior to serialization will reference two separate but equal objects after being written to space.
AbstractEntry is a basic implementation of the Entry interface. This class is often used
as the base class for defining entries because it adheres to the Entry specification. For instance,
it implements the proper semantics for equality and comparisons.
Now that you have reviewed entries and their role in JavaSpaces, let's define a CommandEntry
to read and write objects to a space that contains objects that implement the Command interface.
public class CommandEntry extends AbstractEntry {
public String name;
public Command cmd;
public CommandEntry() {
this(null);
}
public CommandEntry(String name) {
this(name,null);
}
public CommandEntry(String name, Command cmd) {
this.name = name;
this.cmd = cmd;
}
}
The CommandEntry extends AbstractEntry and contains a name field used to name the
associated command and a reference to the Command object. Also provided is the required no
argument constructor. The fields of the CommandEntry are also serializable, so all the
95
requirements of the Entry specification have been met. What follows is a demonstration of how
easy it is to write entries to space.
Retrieving entries from JavaSpaces requires a template of the type or subtype of the entry to be
built. Entries are compared, public field by public field, based on the template. If two fields have
the same value, they are considered equal. When you want to retrieve an Entry from
JavaSpace, you need to create a template for the system to compare for equality. For example:
CommandEntry template = new CommandEntry("FileList");
The preceding fragment constructs a CommandEntry template that can be used to match and
return any command that has been named "FileList" in the JavaSpaces server.
CommandEntry template = new CommandEntry();
This second template can be used to match and return any command of any name. The null
value acts as a wildcard in matching fields in an Entry. The null value will therefore match
any and all values. There are a couple of implications as a result of this idiom.
First, you cannot use null to match on an uninitialized value. You need to introduce another
field, such as a Boolean, to perform such a comparison for each field in your Entry
definition. That is because, as mentioned, null matches on anything. In addition, matching on
null or for that matter any field, makes no guarantee as to which object will be returned as a
result of the match.
Suppose you have 10 commands that have been named "FileList," all with slightly different
implementations in the space. If you fetch the command 10 different times using just the
"FileList" argument, you might get:
•
•
•
The same command object returned 10 times
A different command object returned each time
Some combination of repetitive and unique command objects
JavaSpaces makes no guarantee on which object will be returned. This has enormous implications
on the design of your entries and the applicability of JavaSpaces as a solution.
If you normally deal with traditional database technologies, this requirement might catch you off
guard the first time you find yourself looking for a collection or set of objects with a specific value.
In the relational world you do this type of programming all the time. For example, "Bring me back
all rows where column x equals y." The set returned permits us to iterate through each record.
Converting this table/data structure to an Entry will almost always fail.
Entries are designed to be used when exact-match lookup semantics are useful. In other words,
you supply the value you are looking for in the template you provide to the lookup. However, this
should not be construed to mean "I need to know the entire value of the entry in order to find the
one that matches." The wildcard value (null) provides the "I don't care" or "any will do"
semantics for a specific field as previously stated.
JavaSpaces also provides us with the powerful capability to match on subtypes. All fields added
by a subtype are considered to be wildcards. This enables a template to match entries of any of its
subtypes. This capability, combined with dynamic downloading of code, provides a natural
solution to the evolution of service functionality. New class structures can be defined without
affecting current applications using old interface semantics. Or, new functionality can be
dynamically invoked by downloading new class definitions on demand.
96
You will be building a number of sample applications in Part III of this book. These examples will
further refine and define the use of entries and distributed data structures in JavaSpaces.
The Simple API
The JavaSpaces API is very simple. All operations are invoked on an object that implements the
JavaSpace interface. For example, the following code fragment will write an entry of type
CommandEntry into the JavaSpaces service referred to by the identifier space:
// Gain access to an instance of JavaSpace
JavaSpace space = getSpace();
// Create an entry
CommandEntry entry = new CommandEntry();
// initialize the public fields
entry.name = "FileList";
entry.cmd = new FileList("/usr/files/jini1_1");
// write the entry to space and allow it to exist for 1 minute
space.write(entry, null, 60 * 1000);
The JavaSpace interface is defined as:
Listing 5.1 The JavaSpace Interface
package net.jini.space;
import
import
import
import
java.rmi.*;
net.jini.core.event.*;
net.jini.core.transaction.*;
net.jini.core.lease.*;
public interface JavaSpace {
public final long NO_WAIT = 0; // don't wait at all
Lease write(Entry e, Transaction txn, long lease)
throws RemoteException, TransactionException;
Entry read(Entry tmpl, Transaction txn, long timeout)
throws TransactionException, UnusableEntryException,
RemoteException, InterruptedException;
Entry readIfExists(Entry tmpl, Transaction txn,
long timeout)
throws TransactionException, UnusableEntryException,
RemoteException, InterruptedException;
Entry take(Entry tmpl, Transaction txn, long timeout)
throws TransactionException, UnusableEntryException,
RemoteException, InterruptedException;
Entry takeIfExists(Entry tmpl, Transaction txn,
long timeout)
throws TransactionException, UnusableEntryException,
97
RemoteException, InterruptedException;
EventRegistration notify(Entry tmpl, Transaction txn,
RemoteEventListener listener, long lease,
MarshalledObject handback)
throws RemoteException, TransactionException;
Entry snapshot(Entry e) throws RemoteException;
}
The Transaction and TransactionException types in the preceding signatures are
imported from net.jini.core.transaction. The Lease type is imported from
net.jini.core.lease. The RemoteEventListener and
EventRegistration types are imported from net.jini.core.event.
The Transaction parameter may be null, which means that no Transaction object is
managing the operation. Transactions will be discussed in the next chapter.
The JavaSpace interface is not a remote interface. Each implementation of a JavaSpaces
service exports proxy objects that implement the JavaSpace interface locally on the client,
talking to the actual JavaSpaces service through an implementation-specific interface. An
implementation of any JavaSpace method can communicate with a remote JavaSpaces service.
Each method defined throws RemoteException to allow for possible failures. You should
expect RemoteExceptions on method calls in the same cases in which you would expect
them for methods invoked directly on an RMI remote reference.
The details of each JavaSpace method are given in the sections that follow.
write
A write statement copies an Entry object to a JavaSpaces service.
long lease_requested = 60 * 60 * 1000;
Lease lease = space.write(template, null, lease_requested);
Each write operation places a new entry into the specified space, even if the same Entry
object is used in more than one write.
Each write invocation returns a Lease object. If the requested time is longer than the space is
willing to grant, you will get a lease with a reduced time. When the lease expires, the entry is
removed from the space. The following code fragment demonstrates how to determine whether the
lease granted is different from the lease requested.
long expires = lease.getExpiration();
if(expires < lease_requested) {
// code to handle case where lease is less than requested
}
If a write returns without throwing an exception, that entry is committed to the space. If a
RemoteException is thrown, the write might or might not have been successful. If any
other exception is thrown, the entry was not written into the space.
Writing an entry into a space can also generate notifications to registered objects (notify will
be discussed a little later).
98
read and readIfExists
The two forms of the read request—read and readIfExists— will search the
JavaSpaces service for an entry that matches the template provided as an Entry.
// create a template setting the name field but leaving the Command
null
String name = "FileList"
CommandEntry template = new CommandEntry(name);
CommandEntry cmdEntry = (CommandEntry)space.read(template, null,
TIMEOUT_VALUE);
If a match is found, a reference to a copy of the matching entry is returned. If no match is found,
null is returned. Passing a null reference for the template will match any entry.
A readIfExists request will return a matching entry, or null if there is currently no
matching entry in the space.
CommandEntry template = new CommandEntry(name);
CommandEntry cmdEntry = (CommandEntry)space.readIfExists(template,
null, TIMEOUT_VALUE);
If the only possible matches for the template are involved in transactions that have conflicting
locks, the timeout value specifies how long the client is willing to wait for interfering
transactions to commit before returning a value. At the end of that time, if no value can be
returned that would not interfere with transactional state, null is returned.
A read request acts like a readIfExists, except that it will wait until a matching entry is
found or until transactions commit, whichever is longer, up to the timeout period.
In both read methods, a timeout of NO_WAIT means to return immediately, with no waiting,
which is equivalent to using a zero timeout.
take and takeIfExists
The take and the takeIfExists requests perform exactly like the corresponding read
and readIfExists requests, except that the matching entry is removed from the space. If a
take or takeIfExists returns a non-null value, the entry has been removed from the
space.
notify
A notify request registers interest in future incoming entries that match the specified template
to the JavaSpaces service. Matching is done as it is for read.
When matching entries are written, the specified RemoteEventListener will be notified.
When you invoke notify, you provide an upper bound on the lease time, which is how long
you want the registration to be remembered by the JavaSpaces service.
Each notify returns a net.jini.core.event.EventRegistration object.
When an object is written that matches the template supplied in the notify invocation, the
listener's notify method is invoked with a RemoteEvent object.
99
snapshot
The process of writing an object to space involves serialization. Serializing an entry for
transmission to a JavaSpaces service will be identical if the same entry is used twice. This is most
likely to be an issue with templates that are used repeatedly to search for entries with read or
take.
The snapshot method gives the JavaSpaces service implementor a way to reduce the impact of
repeated use of the same entry. Invoking snapshot with an Entry will return another
Entry object that contains a snapshot of the original entry. Using the returned snapshot entry is
equivalent to using the unmodified original entry in all operations on the same JavaSpaces service.
So read, write, take, notify, and snapshot comprise the entire JavaSpace API.
Simple, but extremely powerful in developing distributed applications.
Distributed Data Structures
JavaSpaces is built using distributed data structures. Distributed data structures enable multiple
processes to access and manipulate the content of a structure in parallel. Distributed data
structures require a different approach to data access and control. In a typical database system,
processes are defined that act as barriers or locks at a level in the structure that inhibits concurrent
access. This is done to ensure that the database remains in a consistent state, often referred to as
supporting the ACID properties—atomicity, concurrency, isolation, and durability. Other
programming techniques include defining manager objects that provide a barrier around data or
files that ensure data is accessed serially. Distributed data structures offer a number of advantages
to traditional database processing by enabling multiple processes to access and change information
concurrently.
You might want to build a distributed database structure whenever the following requirements
must be met:
•
•
•
•
Scale to large, rapidly growing user populations
High availability over an unreliable network, even during partial failure
Maintain user data consistency across a large user base
Improve or ease operational management of large data structures
In the book JavaSpaces Principles, Patterns, and Practice by Freeman, et al. (Addison Wesley),
there is a significant amount of detail provided on building and using distributed data structures in
JavaSpaces. The book classifies distributed data structures according to common usage patterns.
Shared Variables
A shared variable enables multiple processes to change the shared value in an atomic manner.
JavaSpaces provides an easy approach to support such changes. For instance, the following entry
definition represents an index into a data structure.
Listing 5.2 The Index Class
public class Index implements AbstractEntry {
public Integer value;
public String name;
public Index() { }
100
public increment() {
value = new Integer(value.intValue()+1);
}
public decrement() {
value = new Integer(value.intValue()-1);
}
}
The following code fragment takes the index entry and increments it by using the take and
write semantics of the JavaSpace API.
Index template = new Index();
Index index = (Index)space.take(template, null, Long.MAX_VALUE);
index.increment();
space.write(index, null, Lease.FOREVER);
The take method removes the entry from space. Any process trying to read the entry will be
blocked until the entry is returned with the write method. Therefore, serialization of updates
across distributed processes is guaranteed. You can use the index entry to iterate through ordered
structures, such as distributed arrays (discussed in the following section). By following the take,
then write protocols for updating the structure, the ACID properties for data reliability are
assured. This is all under the control of JavaSpaces and simplifies the development task for the
programmer.
The astute reader will note that the transaction parameter is missing in the previous calls. This
could present problems unless the appropriate error recovery is implemented. More is said in
regard to transactions in the next chapter.
Ordered Structures
Ordered structures are collections that have an index or position field defined in each entry of the
collection. If you are defining a process that might need to iterate through the entries of a space or
require a certain sequencing for the entries, then ordered structures provide one solution.
Distributed Arrays
Distributed arrays are the most popular example of an ordered structure. They are built from space
entries that include an index and name reference.
The distributed array enables you to access individual elements within the array by going directly
to an element via the position index. No locking is required at a start or root element. This permits
and promotes concurrent access, and thus reduces wait and blocking issues as shown in Figure 5.2.
Figure 5.2. Traditional databases use barriers to restrict concurrent access.
Distributed data structures promote concurrent access.
101
In addition to the position field, you need to define a name field. The name field provides a
mechanism to reuse the index entry across multiple data structures as opposed to subtyping the
index entry for each structure. However, either approach—associating a named-index with a data
structure, or subtyping per data structure—is valid. The naming approach reduces the amount of
object types that result in the space.
Unordered Structures
Unordered structures are used when sequence or order is not important as in Figure 5.3. For
instance, workflow applications are often based on a Task entry that is written to space. Worker
processes do not care which Task they take from space, as they can operate or use any task that
they retrieve. The idea is that as the number of tasks grows, so can the number of worker
processes that take and process task objects. The system can expand and contract based on
demand. Workers will often write a Result object to space on completion, which is read or
taken by a Boss or TaskManager object. The manager is responsible for controlling or
aggregating the results from many tasks.
Figure 5.3. Unordered structures are used when sequence is not important. In
effect, the structure implements "any object will do" semantics.
102
Bags
Unordered structures are referred to as bags because you can throw any type of object into them
without concern for the sequencing of the structure.
Application Basics
Developing applications with JavaSpaces requires both of the following:
•
•
Experience with synchronization techniques in distributed processing
Experience defining data structures supporting loosely coupled communication
JavaSpaces goes a long way toward minimizing the learning curve required to gain this experience.
Synchronization Techniques
The basics of space-based programming synchronization can be quickly summarized thusly:
Multiple processes can read an entry in a space at any time. But when a process wants to update an
entry, it first has to remove it (take) from the space and thereby gain sole access to it. In other
words, the read, take, and write operations enforce coordinated access to entries. Entries
and their operations give you everything you need to build more complex synchronization and
object coordination.
Loosely Coupled Communication
JavaSpaces provides a simple foundation for the exchange of information between loosely coupled
processes. For example, by writing a message to a space, any process can read or take the message
from that space.
Combined with the Jini discovery process, it is easy to recognize the power and flexibility of
dynamically discovering a space and interfacing with it. In addition, coupled with RMI, the
options for exchanging data and objects become almost limitless.
103
Deploying JavaSpaces
Let's now deploy JavaSpaces into the environment to demonstrate the simplicity and power of the
technology through the following examples.
JavaSpaces requires the use of a transaction service like the Jini-supplied mahalo
service. The transaction service provides support for ACID properties through the two-phase
commit protocol. You must start the mahalo transaction service prior to starting JavaSpaces. We
will discuss transactions in the next chapter. For now, simply start the mahalo service supplied
with Jini.
The following script starts the mahalo transaction service. It can be found in the services\scripts
directory. Simply invoke the startMahalo.cmd. You can use the setup.cmd file to
ensure your environment can run the scripts. If you prefer you can use the GUI-supplied service
starter that you used in Chapter 3, "Supporting Technologies."
C:\JiniJavaSpaces\services\scripts\startMahalo
Listing 5.3 startMahalo.cmd Script
@echo off
@call setup.cmd
set GROUPS=public
echo
echo
echo
echo
echo
echo
echo
echo
Jini and JavaSpaces Application Development
------------------------------------------Jini install directory
%JINI_HOME%
Examples install directory
%JWORK_HOME%
Web server
%HTTP_ADDRESS%
Default group
%GROUPS%
------------------------------------------Starting the mahalo transaction service...
java -jar Djava.security.policy=%JINI_HOME%\example\lookup\policy.all
-Dcom.sun.jini.mahalo.managerName=TransactionManager
%JINI_HOME%\lib\mahalo.jar http://%HTTP_ADDRESS%/mahalo-dl.jar
%JINI_HOME%\example\txn\policy.all %JWORK_HOME%\services\logs\txn_log
%GROUPS%
Sun provides a reference implementation of the JavaSpaces Service called outrigger. You can run
outrigger as an activatable service or as a transient service.
Which version you use will depend on your requirements for persistence. In other words, will you
require the persistence service state to survive restarts or system crashes? The transient space does
not provide persistence across system restarts. So, if the system crashes or you need to restart the
service, the information that is stored in the transient space will not be available.
The following script starts the outrigger persistent service:
C:\JiniJavaSpaces\services\scripts\startOutrigger
104
Listing 5.4 The startOutrigger.cmd Script
@echo off
@call setup.cmd
set GROUPS=public
set SPACENAME=JavaSpaces
echo
echo
echo
echo
echo
echo
echo
echo
echo
Jini and JavaSpaces Application Development
------------------------------------------Jini install directory
%JINI_HOME%
Examples install directory
%JWORK_HOME%
Web server
%HTTP_ADDRESS%
Default group
%GROUPS%
Default SpaceName property
%SPACENAME%
------------------------------------------Starting the outrigger JavaSpaces service...
java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all
-Dcom.sun.jini.outrigger.spaceName=%SPACENAME%
%JINI_HOME%\lib\outrigger.jar http://
%HTTP_ADDRESS%/outrigger-dl.jar %JINI_HOME%\policy\policy.all
%JWORK_HOME%\services\logs\js_log GROUPS
Notice the -Dcom.sun.jini.outrigger.spaceName property, which is used to
name this instance of outrigger.
In order to start outrigger, your Jini environment will need the following:
•
•
•
•
An HTTP server running with a public directory available for downloading files.
An RMI daemon (rmid) running to activate services
An LUS (reggie) running to register and lookup services
The transaction service (mahalo) required for JavaSpace transaction support
One thing to note is that after your environment is in place, subsequent startups only require the
first two services—an HTTP server for downloading code, and the RMI daemon for activating
services. Because reggie, mahalo, and outrigger can all be started as activatable services, they will
be restarted by RMI automatically when rmid starts up.
Figure 5.4 shows one of many possible configurations for testing the next example. If you have
access to multiple machines (which is highly recommended), you should experiment with
different deployment alternatives. Of course, you can get this example to work on a single
machine, but once you do, try running the services using different configurations.
Figure 5.4. An sample configuration for testing the JavaSpaces applications in this
chapter. Although the examples can be run on a single machine, configuring and
developing on the network is recommended.
105
Figure 5.4 depicts separate machines running on different network segments. You have deployed
your HTTP server, rmid, and reggie on a single server, while JavaSpaces and the transaction
service are running on a different server and segment. Then you test your message producing pad
(discussed later in this chapter) and message consuming shell on different workstations.
The process to access the JavaSpaces service is like accessing any other Jini service; you perform
the lookup process with an LUS that has the JavaSpaces service registered.
The net.jini.space package provides the service interface and related classes for the
Sun/Jini outrigger implementation.
Listing 5.5 The SpaceAccessor Class
package org.jworkplace.util;
import java.rmi.*;
import java.util.*;
import net.jini.space.JavaSpace;
import net.jini.core.entry.*;
import net.jini.core.lookup.*;
import net.jini.lookup.entry.*;
import net.jini.core.discovery.*;
/*
* The SpaceAccessor class is a utility class used to resolve the
reference
* to the outrigger JavaSpace service
*/
public class SpaceAccessor {
// host name running LUS and name of JavaSpace instance
public synchronized static JavaSpace getSpace(String hostname,
String name)
{
106
try {
if (System.getSecurityManager() == null) {
System.setSecurityManager(
new RMISecurityManager());
}
// unicast discovery
LookupLocator lookup = new LookupLocator("jini://" +
hostname);
System.out.println("SpaceAccessor using locator: " +
lookup);
ServiceRegistrar registrar = lookup.getRegistrar();
// create Name entry to match on
Entry entries[] = { new Name(name) } ;
// lookup the service in the LUS
JavaSpace space = (JavaSpace)registrar.lookup(new
ServiceTemplate(null,null,entries));
}
// return the proxy
return space;
catch (Exception e) {
System.err.println(e);
}
return null;
}
}
This first iteration of the utility only supports unicast discovery. So, to use this utility, you must
know the location of the LUS that has the JavaSpaces instance running as referenced by the
hostname parameter. The name argument refers to the -Dcom.sun.jini.outrigger.
spaceName=JavaSpaces property that was used to launch JavaSpaces.
A Sample Application—Building the MessagePad
You can quickly build an example that permits a message to be sent through space. In the
following example, a take operation is used to retrieve and delete messages as they arrive into
space.
The simple message definition extends AbstractEntry and adds a public field to store text.
Listing 5.6 The Message Class
package org.jworkplace;
import net.jini.entry.AbstractEntry;
/**
* The Message class is used to create messages for a JavaSpace.
*/
public class Message extends AbstractEntry {
public String text;
107
public Message() { }
public Message(String text) {
super();
this.text = text;
}
}
The MessagePad class can be used to write single-line messages to a JavaSpaces service. The
first argument is the hostname where a lookup service is running that has a registered instance of
JavaSpaces. The second argument is the name of the JavaSpaces instance. It must be a JavaSpaces
service that has been registered with the appropriate Name entry, such as JavaSpaces.
Listing 5.7 The MessagePad Class
package org.jworkplace;
import java.io.*;
import net.jini.space.JavaSpace;
import org.jworkplace.util.SpaceAccessor;
public class MessagePad {
public static void main(String[] args) {
String spacename = null, hostname = null;
if(args.length < 2) {
System.out.println("Usage java org.jworkplace.MessagePad
[hostname]
[spacename]");
}
System.exit(1);
else {
hostname = args[0];
spacename = args[1];
}
try {
// get the space proxy
JavaSpace space = SpaceAccessor.getSpace(hostname,
spacename);
if(space != null) {
// read input from the console
String line;
BufferedReader reader = new BufferedReader(new
InputStreamReader(System.in));
// create a message entry
Message msg = new Message();
// while console input available
while ((line = reader.readLine()).length() > 0) {
// set the message text
msg.text = line;
// write a message per line entered
space.write(msg, null, Long.MAX_VALUE);
}
}
else {
System.out.println("Unable to find: " + spacename);
108
}
System.exit(1);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
The MessageShell class is used to take the messages written by the MessagePad to the
JavaSpaces service and displays them on the console. Again, the first argument is the hostname
where JavaSpaces has registered with an LUS, and the second argument is the name of the
JavaSpaces instance.
Listing 5.8 The MessageShell Class
package org.jworkplace;
import net.jini.space.JavaSpace;
import org.jworkplace.util.SpaceAccessor;
public class MessageShell {
public static void main(String[] args) {
String spacename = null, hostname = null;
if(args.length < 2) {
System.out.println("Usage java org.jworkplace.MessageShell
[hostname]
[spacename]");
}
System.exit(1);
else {
hostname = args[0];
spacename = args[1];
}
try {
JavaSpace space = SpaceAccessor.getSpace(hostname,
spacename);
if(space != null) {
// Retrieve any Message in the space
Message template = new Message();
for(;;) {
// take each message from the space
// the call will block until a message arrives
Message received =
(Message)space.take(template, null ,
Long.MAX_VALUE);
}
System.out.println(received.text);
}
else {
// we were unable to find the JavaSpaces service
specified
System.out.println("Unable to find: " + hostname + " "
+
spacename);
System.exit(1);
}
109
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Figure 5.5. Messages can be easily exchanged by using the write and take
operations of the JavaSpace API.
Step-By-Step
Compile the Files
To build the example, change to the Chapter 5 Example 1 directory and invoke the compile
command.
C:\JiniJavaSpaces\chapter5\example1\compile
Start the MessagePad
To test the message producer, move to the directory and machine where you deployed the client
software (MessagePad). Run the following command:
C:\JiniJavaSpaces\chapter5\example1\>start messageSender hostname
spacename
where:
•
•
hostname is the host running the LUS where the space is registered
spacename is the name of the JavaSpaces instance, such as JavaSpaces
Start the MessageShell
To test the message receiver, move to the directory and machine where you deployed the
MessageShell. Run the following command:
110
C:\JiniJavaSpaces\Chapter5\example1>start messageReceiver hostname
spacename
where:
•
•
hostname is the host running the LUS where the space is registered
spacename is the name of the JavaSpaces instance, such as JavaSpaces
You will be able to type messages into the console of the MessagePad and see them displayed
on the console of the MessageShell. A null line terminates message production.
Using Distributed Events with Spaces
JavaSpaces uses the distributed event model of Jini to deliver remote events to remote listeners.
JavaSpaces acts as the source of events when an entry is written to space that matches a template
that has been registered by or for a RemoteEventListener. JavaSpaces compares each
entry written to space to determine whether a RemoteEventListener should be notified of
the existence of a new entry in space that matches a template associated with the registration. The
space sends a RemoteEvent object to the listener by invoking the notify method of the
RemoteEventListener.
You can extend your Message entry to support point-to-point communication using distributed
events.
Extend your Message class to include the from-and-to system identification. This will enable a
client to supply the destination and source of the system exchange. Unlike the MessageShell,
which simply took any message from space, the new RequestListener will be more
selective. In addition, it receives notification of a matching entry using distributed events.
The Request class extends Message and adds the from-and-to system identification.
Listing 5.9 The Request Class
package org.jworkplace;
import net.jini.entry.AbstractEntry;
public class Request extends Message {
public String toSystem;
public String fromSystem;
public Request() { }
}
The RequestListener implements the RemoteEventListener interface and uses
the static UnicastRemoteObject.exportObject method to export itself to the RMI
runtime.
When JavaSpaces produces the RemoteEvent to signal the arrival of a matching Entry, the
space using the RMI runtime will invoke the notify method of RequestListener.
Listing 5.10 The RequestListener Class
package org.jworkplace;
111
import
import
import
import
import
import
java.rmi.*;
java.rmi.server.UnicastRemoteObject;
net.jini.space.JavaSpace;
net.jini.core.event.RemoteEvent;
net.jini.core.event.RemoteEventListener;
org.jworkplace.util.SpaceAccessor;
public class RequestListener implements RemoteEventListener {
private JavaSpace space;
// Source system identification
private String toSystem;
public RequestListener(JavaSpace space, String toSystem)
throws RemoteException
{
this.space = space;
this.toSystem = toSystem;
// export to RMI
UnicastRemoteObject.exportObject(this);
}
// Receive notification when entry arrives in space
public void notify(RemoteEvent event)
{
// create the template using the source system id
Request template = new Request();
template.toSystem = toSystem;
Request request = template;
// loop to take all entries from space
while(request != null) {
try {
request = (Request)space.take(template, null,
Long.MAX_VALUE);
System.out.println(request.text);
} catch (Exception e) { e.printStackTrace(); }
}
}
You have modified the prior version of the MessagePad to include the registration of a
RemoteEventListener with JavaSpaces. Recall that the notify method of the
JavaSpace API is:
EventRegistration notify(Entry tmpl, Transaction txn,
RemoteEventListener listener, long lease,
MarshalledObject handback)
throws RemoteException, TransactionException;
Create the registration with the following code fragment:
// create the space listener passing the source system id
// we use this in the listener to take matching requests
listener = new RequestListener(space, fromSystem);
// register for Requests from destination system
112
Request template = new Request();
template.toSystem = toSystem;
registration = space.notify(template,null,listener,Lease.FOREVER,
null);
In addition, it now requires the command line to include the source and destination system (fromand-to) identification.
Listing 5.11 The MessagePad Class Updated
package org.jworkplace;
import
import
import
import
import
java.io.*;
net.jini.space.JavaSpace;
net.jini.core.event.EventRegistration;
net.jini.core.lease.Lease;
org.jworkplace.util.SpaceAccessor;
public class MessagePad {
public static void main(String[] args) {
// Registration for space notification
EventRegistration registration;
// RemoteEventListener that will receive notification
RequestListener listener;
// Host running the LUS
String hostname = null;
// The
String
// The
String
name of the JavaSpaces instance
spacename = null;
request source system identification
fromSystem = null;
// The request destination system identification
String toSystem = null;
if(args.length < 4) {
System.out.println("Usage [hostname] [spacename] [from] [to]");
System.exit(1);
} else {
hostname = args[0];
spacename = args[1];
fromSystem = args[2];
toSystem = args[3];
}
// unicast discovery setup
JavaSpace space = SpaceAccessor.getSpace(hostname, spacename);
if(space != null) {
try {
// create the space listener passing the id
listener = new RequestListener(space, fromSystem);
// register for Requests from destination system
Request template = new Request();
template.toSystem = toSystem;
113
registration = space.notify(template, null, listener, Lease.FOREVER,
null);
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
// Create messages from console input to destination system
String line;
try {
BufferedReader reader = new BufferedReader(new
InputStreamReader(System.in));
while ((line = reader.readLine()).length() > 0) {
Request request = new Request();
request.toSystem = toSystem;
request.fromSystem = fromSystem;
request.text = line;
space.write(request, null, Long.MAX_VALUE);
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("Unable to find: " + spacename);
System.exit(1);
}
}
}
Compile the Files
To build the example, change to the Chapter 5 Example 2 directory and invoke the compile
command.
C:\JiniJavaSpaces\chapter5\example2\compile
Create Jar File
Now invoke the deploy.cmd script to generate the jar file with the
RequestListener_Stub.class. The script also copies the jar file to the Web server
directory.
C:\JiniJavaSpaces\Chapter5\example2>deploy
Figure 5.6. The MessagePad writes messages to JavaSpace. The
RequestListener objects are notified of the arrival of messages that match a
template. The template is initialized with a system identifier, such as a uri.
114
Listing 5.12 The deploy.cmd Script
@echo off
@call ..\..\services\scripts\setup.cmd
echo Jini and JavaSpaces Application Development
echo Chapter 5 Example 2
echo ------------------------------------------echo Jini install directory
%JINI_HOME%
echo Examples install directory
%JWORK_HOME%
echo Web server
%HTTP_ADDRESS%
echo ------------------------------------------echo Creating jar file chap5ex2-dl.jar in directory
%JWORK_HOME%\services\lib
echo ...
jar cvf %JWORK_HOME%\services\lib\chap5ex2-dl.jar -C org/jworkplace/
/
RequestListener_Stub.class
Start the MessagePad
To test the request listener, run the following command:
C:\JiniJavaSpaces\Chapter5\example2>start messageSender hostname
spacename fromname toname
where:
•
•
•
•
hostname is the host running the LUS where the space is registered
spacename is the name of the JavaSpaces instance, such as JavaSpaces
fromname is the name of the source system, such as www.jworkplace.org
toname is the name of the destination system, such as www.sams.com
Now start another instance of the MessagePad:
115
C:\JiniJavaSpaces\Chapter5\example2>start messageSender hostname
spacename fromname toname
where:
•
•
•
•
hostname is the host running the LUS where the space is registered
spacename is the name of the JavaSpaces instance, such as JavaSpaces
fromname is the name of the source system, such as www.sams.com
toname is the name of the destination system, such as www.jworkplace.org
You should be able to type messages into the console of one MessagePad and see the
messages displayed on the console of the other MessagePad. You now have a simple, point-topoint framework for exchanging requests and responses through JavaSpaces. You can start any
number of MessagePads using different from-and-to address identifications and be able to
communicate through the space service. The space will queue the messages until a requesting
MessagePad takes the requests from space.
Channel Data Structures for Communication
The last example in this chapter demonstrates a chat program. In this example, you will build on
the concept of a channel. A channel is an ordered set of messages—an implementation of a
distributed data structure that has an index or position field defined in each entry of the collection.
This allows you to sequence the messages in the collection and to iterate through the entries.
The new ChatMessage class extends Message by providing two new fields: a channel
name and a position. The name is used to uniquely identify the channel in a specific instance of
JavaSpace. The position is used to indicate where in the channel sequence the message resides.
The text field contains the actual message data.
Listing 5.13 The ChatMessage Class
package org.jworkplace;
import net.jini.entry.AbstractEntry;
/*
* The Message class is used to create messages for a chat channel.
*/
public class ChatMessage extends Message {
public String channel;
public Integer position;
public ChatMessage()
{ }
public ChatMessage(String channel, Integer position, String text)
{
this.channel = channel;
this.position = position;
this.text = text;
}
}
The combination of the channel name and the position uniquely identifies a chat message in space.
In addition, because you sequentially increment the position for each message written to a channel,
116
you can determine the number of messages in a channel by starting at the first message and
incrementing the position with each read until a null is returned.
You also create a Tail entry to indicate the last entry in the channel. If you have worked with
queues before, then the heads and tails technique to indicate the start and end of a queue structure
should not be new. However, what might be new is having a named tail that equals the named
queue (channel).
Listing 5.14 The Tail Class
package org.jworkplace;
import net.jini.entry.AbstractEntry;
public class Tail extends AbstractEntry {
public String name;
public Integer position;
public Tail() {
super();
this.name = null;
this.position = null;
}
public int increment() {
position = new Integer(position.intValue() + 1);
return position.intValue();
}
public int decrement() {
position = new Integer(position.intValue() - 1);
return position.intValue();
}
}
The ChatController class uses ChatMessage and Tail entries to enable space chat.
If a Tail entry for the designated channel does not exist, create a new channel.
Listing 5.15 The ChatController Class
package org.jworkplace.viewers;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import net.jini.space.JavaSpace;
import net.jini.core.lease.*;
import org.jworkplace.*;
/**
* The ChatController class is used to create the ChatView and
control the
* reading and writing of messages to the chat channel
*/
public class ChatController implements ActionListener {
// The name of the channel
private String channel = null;
117
// name of user in chat session
private String userName = null;
// Simple Swing JPanel
private ChatViewer viewer = null;
// Thread to read new messages
private ChannelListener cl = null;
private volatile Thread listener;
// Messages exist for 1 hour
public long CHAT_TIME = 60 * 60 * 1000; // 1 hour
private JavaSpace space;
private JFrame frame;
public ChatController(JFrame frame, JavaSpace space, String
channel, String user) {
this.frame = frame;
this.space = space;
this.userName = user;
this.channel = channel;
viewer = new ChatViewer(this,channel);
// start the channel listener thread
cl = new ChannelListener();
cl.start();
// determine if there is already a session open
if(!activeChannel())
createChannel();
}
// Does Tail entry exists for this channel?
synchronized boolean activeChannel() {
Tail template = new Tail();
template.name = channel;
try {
Tail tail = (Tail)space.readIfExists(template, null,
JavaSpace.NO_WAIT);
if(tail == null) return false;
} catch (Exception e) { }
return true;
}
// if no active channel create and initialize new Tail
private void createChannel() {
Tail tail = new Tail();
tail.name = channel;
showStatus("Creating channel "+ channel);
tail.position = new Integer(0);
try {
Lease lease = space.write(tail, null, CHAT_TIME);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
118
When you press the send button, an ActionEvent is triggered and delivered to the
actionPerformed method. The message is displayed in a JTextArea and appended to
the channel, meaning it is written to space.
// Action to display message and append to channel
public void actionPerformed(ActionEvent event) {
Object object = event.getSource();
if (object == viewer.chatControl) {
String message = userName+"> "+viewer.getMessage();
if (message.equals("")) {
JOptionPane.showMessageDialog(frame, "Enter message");
return;
}
viewer.setMessage("");
append(channel, message);
}
}
// append message to channel
private void append(String channel, String msg) {
// get the next available position
Integer messageNum = getMessageNumber();
// create a new message using the new position
ChatMessage message = new ChatMessage(channel, messageNum,
msg);
try {
// write to space
Lease lease = space.write(message, null, CHAT_TIME);
} catch (Exception e) { e.printStackTrace(); return; }
}
The getMessageNumber method determines whether a Tail exists. If a Tail entry exists,
you take the Tail from space, increment the position, and write it back. While you have taken
the Tail entry, no other process will be able to read the Tail. Here again, JavaSpaces provides
an easy technique for serializing distributed processes.
If the Tail does not exist, create a new channel.
// get the current tail and increment
private Integer getMessageNumber() {
try {
Tail template = new Tail();
template.name = channel;
Tail tail = (Tail)space.take(template, null, 10 * 1000);
// If no tail exists create a new channel
if (tail == null) {
createChannel();
tail = (Tail) space.take(template, null, Long.MAX_VALUE);
}
// increment the tail position
tail.increment();
// write the tail to space
Lease lease = space.write(tail, null, CHAT_TIME);
// return the next position
return tail.position;
} catch (Exception e) {
e.printStackTrace();
return null;
}
119
}
public JPanel getChatView() {
return viewer; }
On a WindowClosingEvent you interrupt and kill the channel listener thread.
// set the listener to null and interrupt the thread
public void windowClosing() {
listener = null;
cl.interrupt();
}
The ChannelListener thread is an internal class. It reads messages on startup that already
exist in the channel if you are joining an active session.
The run method simply blocks on read requests, incrementing the position after each read, then
waits for the next message to arrive or until it's interrupted.
Listing 5.16 The ChannelListener Class
// The channel listener
public class ChannelListener extends Thread
int position = 1;
String newline;
{
public ChannelListener() {
newline = System.getProperty("line.separator");
// If joining an existing chat display chat history
if(activeChannel()) {
try {
ChatMessage template = new ChatMessage();
template.channel = channel;
ChatMessage msg = template;
// loop through all messages starting at 2
while(msg != null) {
template.position = new Integer(position++);
msg = (ChatMessage)space.readIfExists(template,
null,
JavaSpace.NO_WAIT);
if(msg != null) {
viewer.append(msg.text + newline);
} else {
position--;
}
}
}
catch (Exception e) {
e.printStackTrace(); }
}
}
// run until interrupted
public void run() {
listener = Thread.currentThread();
while(listener != null) {
ChatMessage template = new ChatMessage();
template.channel = channel;
ChatMessage msg = null;
// increment the current position
template.position = new Integer(position++);
120
try {
// wait till message arrives
msg = (ChatMessage)space.read(template, null,
Long.MAX_VALUE);
}
// display new message
viewer.append(msg.text + newline);
catch (Exception e) { }
}
}
}
}
The ChatFrame class contains the main method that accepts the command line arguments,
resolves the JavaSpace reference using the SpaceAccessor utility, and creates the view
controller.
Listing 5.17 The ChannelFrame Class
package org.jworkplace.viewers;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.rmi.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import net.jini.space.JavaSpace;
import org.jworkplace.*;
import org.jworkplace.util.SpaceAccessor;
public class ChatFrame extends JFrame {
public static void main(String[] args) {
if(args.length < 4) {
System.out.println("Usage [hostname] [spacename] [channel]
[user]");
System.exit(1);
}
ChatFrame frame = new ChatFrame(args[0], args[1], args[2],
args[3]);
}
public ChatFrame(String hostname, String spacename, String
channel, String user)
{
super("JWorkPlace");
JavaSpace space = SpaceAccessor.getSpace(hostname,
spacename);
addWindowListener(new WindowEventHandler());
getContentPane().setBackground(Color.black);
getContentPane().add(new ChatController(this, space,
channel,user).getChatView());
setSize(480,640);
setVisible(true);
}
121
class WindowEventHandler extends WindowAdapter {
public void windowClosing(WindowEvent evt) {
System.exit(0);
}
}
}
Figure 5.7 depicts a chat channel with four active messages. The chat controller would append the
next message using position five. Each participant (ChannelListener) in the chat is
blocking on a read request using a template with position five. When the entry is written, each
participant will read the entry and increment the template position number to block on position six.
Figure 5.7. A distributed array structure can be used to implement a message
channel. The channel is used to sequence messages and control access to
message content.
Now let's build your chat program. The chat program will provide the capability to send messages
on a named channel. Any number of participants can share the channel. The user sending the
message is identified on the display panel.
Compile the Files
To build the example, change to the Chapter 5 example 3 directory and invoke the compile
command.
C:\JiniJavaSpaces\chapter5\example3\compile
Start the Chat Control
You specify the channel and your user identification when you start the chat program.
C:\JiniJavaSpaces\Chapter5\example3>start chat hostname spacename
channel user
where:
122
•
•
•
•
hostname is the host running the LUS where the space is registered
spacename is the name of the JavaSpaces instance, such as JavaSpaces
channel is the name of the chat session to join, such as movies
user is your name displayed in the session
Now start another instance of the chat controller (see Figure 5.8). Use the same channel name, but
change the user identification. Type your message and send!
Figure 5.8. The Chat Program can be used as a simple Instant Messenger using
JavaSpaces.
Summary
JavaSpaces is a Jini service that supports distributed persistence and the design of distributed
algorithms.
The JavaSpace API is simple, yet provides much of the common functionality required for
distributed systems. It simplifies the development task, particularly when you can model your task
as a flow of objects between systems distributed over a network.
JavaSpaces is built from distributed data structures, such as shared variables, unordered
collections, and distributed arrays.
Sun provides a reference implementation of the JavaSpaces Service called outrigger.
JavaSpaces provides a simple foundation for the exchange of information between loosely coupled
processes. Combined with the Jini discovery process, it is easy to recognize the power and
flexibility of dynamically discovering a space and interfacing with it. In addition, coupled with
RMI, the options for exchanging data and objects become almost limitless.
123
Chapter 6. Transaction Service
IN THIS CHAPTER
•
•
•
•
•
Distributed Transactions
The Jini Transaction Framework
The Mahalo Implementation
Transactional Shell Example
Summary
In the last chapter we introduced the JavaSpaces service. In order to run the Jini-supplied
JavaSpaces service (outrigger), you needed to start a transaction service. In this chapter, we will
discuss the Jini Transaction API and the Transaction Service (mahalo) supplied with Jini.
In general, transactional systems let you group together a set of operations so that either all the
operations complete, or none of them do. Without this transactional capability, the states of
systems can easily become inconsistent. Distributed systems in particular can suffer because
participants can crash the network or leave the network before an operation has completed. By
using transactions, you can ensure that the operations do complete, or if they don't, that the state of
the participating resources remains unchanged.
Distributed Transactions
Distributed transactions have traditionally been tightly coupled with database management
systems and transaction processing monitors (TP Monitor). Transactions and transactional
integrity are not new issues in distributed computing. However, traditional solutions have tended
to be heavyweight and complex.
Most distributed transaction processing has standardized on the two-phase commit protocol. The
two-phase commit protocol defines the communication exchange that enables distributed systems
and resources to wrap a set of operations in such a way that they appear to be a single operation.
The protocol requires a coordinator or transaction manager that guarantees all transactional
participants will eventually know whether they should commit the operations (roll forward), or
abort them (roll backward). A participant can be any resource that supports the two-phase commit
contract by implementing the appropriate interface. Participants are not limited to databases or
other persistent storage services.
A transaction is bracketed by one of these two actions:
•
•
Begin—Commit
Begin—Abort
In phase one (see Figure 6.1), the transaction manager asks all participants whether they are ready
and able to commit a transaction. If a participant responds negatively (vote abort), the participant
will automatically roll back any work it performed on behalf of the transaction and discard any
knowledge it had of the transaction.
Figure 6.1. The two-phase commit protocol is used by the Jini transaction manager
to coordinate participants in a distributed transaction.
124
In phase two, the transaction manager determines whether there are any negative replies, and if so,
instructs all participants to roll back. If all replies are positive (vote commit), it will instruct the
participants to commit.
The two-phase commit protocol attempts to ensure the ACID properties of transactions.
The ACID properties are as follows:
•
•
•
•
Atomicity—All the operations grouped under a transaction occur or none of them do.
Atomicity is defined from the perspective of the consumer of the transaction.
Consistency—The completion of a transaction must leave the system in a consistent state.
If the transaction cannot achieve a stable end state, it must return the system to its initial
state.
Isolation—Transactions should not be affected by other transactions that execute
concurrently. Participants in a transaction should see only intermediate states resulting
from the operations of their own transaction, not the intermediate states of other
transactions.
Durability—The results of a transaction should be as persistent as the entity on which the
transaction commits.
Database management systems go to great lengths to ensure the ACID properties are enforced. As
a result, the system overhead is generally proportional to the degree of transactional integrity. In
other words, more system resources are required to ensure that reading, writing, and updating data
are accurate in light of failures that might occur.
Jini takes a more lightweight approach to transactions. Jini provides only the interface to
transactions, and this interface closely resembles the two-phase commit protocol. By providing
125
only an object interface to transactions, Jini does not dictate the implementation. The notion of
data consistency with regard to partial failure is promoted through the two-phase commit-based
interface. But the key is the degree to which the implementation will try to ensure ACID
properties despite a partial or catastrophic failure.
What the Jini transaction service primarily provides is the coordination mechanism necessary for
objects to agree on the transaction state. Per the Jini specification, the goal of the system is to
provide the minimum set of protocols and interfaces that will enable objects to implement
transaction semantics.
As you expand your notion of distributed transactions to include a broader array of client devices,
like cell phones, Palm Pilots, and mobile agents, you will require support for a more flexible and
lightweight implementation. Jini provides an API that provides implementation flexibility.
Lightweight Jini implementations will reduce the overhead and time required to deploy
transactional systems. Perhaps like message-oriented middleware (MOM), which provides various
levels of message assurance, the new breed of transactional systems will provide various levels of
transactional integrity.
Transaction Types
There are two primary types of transactions—flat and nested.
Flat transactions perform their work within the boundaries of a single begin-end work bracket.
They tend to be relatively simple and short-lived. Flat transactions provide an excellent fit for
modeling short activities, such as banking deposits and withdrawals. The work might be
performed by many different servers and may be distributed across many different platforms. Jini
comes with a reference implementation (mahalo) that supports flat transactions.
Nested transactions occur at many levels, and involve a root transaction that contains leaves that
also contain transactions, and thus form a tree of transactions. The transaction at the root of the
tree is called the top-level transaction. Leaf transactions only commit if their parent transactions
commit. The leaf transaction is able to see the uncommitted data changes in its parent transaction.
Nested transactions can quickly become complex and require significant coordination. The Jini
API supports nested transactions; however, the mahalo implementation does not, as seen in Figure
6.2.
Figure 6.2. The Jini transaction service supports the notion of flat and nested
transactions. The reference implementation mahalo, however, only provides
support for flat transactions.
126
The Jini Transaction Framework
The classes and interfaces defined by the Jini Transaction Specification are in the packages
net.jini.core.transaction and net.jini.core.transaction.server.
There are three primary roles defined in the framework:
•
•
•
Transaction client—Starts and commits or aborts the transaction.
Transaction manager—Provides the overall transaction coordination and provides the
leased transaction object that is passed to the participants of the transaction.
Transaction participants—Any service which receives the transaction object from the
client and implements the TransactionParticipant interface. The transaction
object is defined as a parameter in any method the service exposes that requires
transaction support.
A transaction is created and overseen by a transaction manager. Each manager implements the
interface TransactionManager. Each transaction is represented by a long identifier that
is unique with respect to the transaction:
•
net.jini.core.transaction.server.TransactionManager—
•
net.jini.core.transaction.TransactionFactory— The factory
•
that creates a transaction object when supplied with a transaction manager. The
transaction object represents the current transaction.
net.jini.core.transaction.Transaction— The interface to the
transaction object returned by the transaction factory.
The interface supported by the transaction manager.
Listing 6.1 TransactionManager Interface
package net.jini.core.transaction.server;
public interface TransactionManager
extends Remote, TransactionConstants
{
public static class Created implements Serializable {
public final long id;
public final Lease lease;
public Created(long id, Lease lease) { ...}
}
Created create(long leaseFor)
throws LeaseDeniedException, RemoteException;
void join(long id, TransactionParticipant part,
long crashCount)
throws UnknownTransactionException,
CannotJoinException, CrashCountException,
RemoteException;
int getState(long id)
throws UnknownTransactionException, RemoteException;
void commit(long id)
throws UnknownTransactionException,
CannotCommitException,
RemoteException;
void commit(long id, long waitFor)
throws UnknownTransactionException,
127
CannotCommitException,
TimeoutExpiredException, RemoteException;
void abort(long id)
throws UnknownTransactionException,
CannotAbortException,
RemoteException;
void abort(long id, long waitFor)
throws UnknownTransactionException,
CannotAbortException,
TimeoutExpiredException, RemoteException;
}
The transaction client first finds a transaction manager. The following code fragment uses the
supplied service registrar to look up the transaction manager on the network.
public static TransactionManager findTM(ServiceRegistrar registrar) {
TransactionManager tm = null;
ServiceTemplate template;
Class[] tmInterface = { TransactionManager.class } ;
template = new ServiceTemplate(null,
tmInterface, null);
try {
tm = (TransactionManager)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return tm;
}
The client asks the manager to create a transaction, typically by using a factory class such as
TransactionFactory to create a transaction object. The transaction object created is then
passed as a parameter when performing operations on a service.
TransactionManager manager = findTM(registrar);
Transaction.Created leasedTxn = TransactionFactory.create(manager,
Lease.FOREVER);
The returned Transaction.Created (an inner class) object holds the transaction object
and the lease associated with the transaction.
Transaction transaction = leasedTxn.transaction;
Lease lease = leasedTxn.lease;
The transaction interface is defined as follows:
Listing 6.2 Transaction Interface
package net.jini.core.transaction;
public interface Transaction {
public static class Created implements Serializable {
public final Transaction transaction;
public final Lease lease;
Created(Transaction transaction, Lease lease) { ...}
}
void commit() throws UnknownTransactionException,
CannotCommitException,
RemoteException;
void commit(long waitFor) throws UnknownTransactionException,
CannotCommitException,
128
TimeoutExpiredException,
RemoteException;
void abort() throws UnknownTransactionException,
CannotAbortException,
RemoteException;
void abort(long waitFor) throws UnknownTransactionException,
CannotAbortException,
TimeoutExpiredException,
RemoteException;
}
The TransactionConstants interface defines constants used in the communication
between managers and participants.
Listing 6.3 TransactionConstants Interface
package net.jini.core.transaction.server;
public interface TransactionConstants {
int ACTIVE = 1;
int VOTING = 2;
int PREPARED = 3;
int NOTCHANGED = 4;
int COMMITTED = 5;
int ABORTED = 6;
}
These correspond to the states and votes that participants and managers go through during the
lifecycle of a given transaction.
For the client, the transaction starts out ACTIVE as soon as create returns. The client drives
the transaction to completion by invoking commit or abort on the transaction manager; or by
canceling the lease or letting the lease expire (both of which are equivalent to an abort).
For the participant, the transaction starts out ACTIVE as soon as join returns. Any operations
attempted under a transaction are valid only if the participant has the transaction in the ACTIVE
state. In any other state, a request to perform an operation under the transaction should fail,
signaling the invoker appropriately.
A transaction completes when any entity either commits or aborts the transaction. If a transaction
commits successfully, then all operations performed under that transaction will complete.
Aborting a transaction means that all operations performed under that transaction will appear
never to have happened.
Committing a transaction requires each participant to vote; where a vote is either prepared (ready
to commit), not changed (read-only), or aborted (the transaction should be aborted). If all
participants vote prepared or not changed, the transaction manager will tell each prepared
participant to roll forward, thus committing the changes. Participants that voted not changed need
do nothing more. If the transaction is ever aborted, the participants are told to roll back any
changes made under the transaction.
In the manager's view, a transaction goes through the following states. When a transaction is
created using create, the transaction is ACTIVE. This is the only state in which participants
may join the transaction. Attempting to join the transaction in any other state throws a
CannotJoinException.
129
Invoking the manager's commit method causes the manager to move to the VOTING state, in
which it attempts to complete the transaction by rolling forward. Each participant that has joined
the transaction has its prepare method invoked to vote on the outcome of the transaction. The
participant may return one of three votes: NOTCHANGED, ABORTED, or COMMITTED.
A timeout can also be specified in the abort and commit methods. If the transaction manager
reaches a decision, but is unable to notify all participants of that decision before the specified
timeout expires, then TimeoutExpiredException is thrown. If the specified timeout
expires before the transaction manager reaches a decision, then
TimeoutExpiredException is not thrown until the manager reaches a decision.
It is the responsibility of the client to maintain the lease to the transaction object. If the lease is not
maintained, the transaction manager will abort the transaction when the lease expires.
The client passes the transaction object to services, which require transactions in method calls,
such as the JavaSpaces API write method seen here:
space.write(msg, transaction, 1000*10);
The first time a client tells a participant to perform an operation under a given transaction, the
participant must invoke the transaction manager's join method with an object that implements
the TransactionParticipant interface. This object will be used by the manager to
communicate with the participant about the transaction.
public void someMethod(Transaction txn) throws RemoteException,
CannotJoinException,
UnknownTransactionException,
CrashCountException
{
// Cast to ServerTransaction (flat transaction)
ServerTransaction serverTxn = (ServerTransaction)txn;
// join the transaction providing a TransactionParticipant and
crash count
serverTxn.join(this, crashCount);
// continue processing
If the participant's invocation of the join method throws RemoteException, the
participant should not perform the operation requested by the client, and should either re-throw the
exception or signal failure to the client.
The TransactionParticipant interface must be implemented by the object referenced
in the first parameter of the join method.
Listing 6.4 TransactionParticipant Interface
package net.jini.core.transaction.server;
public interface TransactionParticipant
extends Remote, TransactionConstants
{
int prepare(TransactionManager mgr, long id)
throws UnknownTransactionException,
RemoteException;
130
void commit(TransactionManager mgr, long id)
throws UnknownTransactionException,
RemoteException;
void abort(TransactionManager mgr, long id)
throws UnknownTransactionException,
RemoteException;
int prepareAndCommit(TransactionManager mgr, long id)
throws UnknownTransactionException,
RemoteException;
}
The TransactionParticipant interface provides the methods that support the twophase commit protocol.
The join method's second parameter is a crash count that uniquely defines the version of the
participant's storage that holds the state of the transaction. Each time the participant loses the state
of that storage (because of a system crash if the storage is volatile, for example) it must increase
the crash count by one. For example, the participant could store the crash count in stable storage.
When the participant is restarted, retrieve the crash count and increase it by one.
When a manager receives a join request, it checks to see whether the participant has already
joined the transaction. If it has, and the crash count is the same as the one specified in the original
join, the join is accepted but is otherwise ignored. If the crash count is different, the manager
throws CrashCountException and forces the transaction to abort.
The Mahalo Implementation
Sun provides a reference implementation of the Jini Transaction Service called mahalo. You run
mahalo as an activatable service. An activatable service is one that registers with rmid and restarts
when rmid is activated. The startMahalo.cmd script can be used to start the mahalo service.
Of course, you can also use the service starter GUI that was introduced in Chapter 3, "Supporting
Technologies." See Appendix B "Tools," for more information on starting services.
Transactions in JavaSpace
In the last chapter we developed applications using JavaSpaces. We always used a null
transaction as the second parameter to the methods defined, which assumes the operation consists
of one indivisible action. As soon as the operation completes, the entry is visible to all clients of
the space. On the other hand, when we write an entry under a non-null transaction, the entry is
not accessible to operations outside of the transaction until the transaction commits. If the
transaction commits, then all the entries written under the transaction become visible to the entire
space. However, if the transaction aborts, the entries written under the transaction are removed. In
effect, after a transaction aborts, the space reflects that the operations never occurred.
When using distributed data structures such as distributed arrays, it is important that transactional
support is enabled. Recall that you use an index and a tail entry to provide sequence support for
distributed data structures. If you take the tail entry, you remove it from space. What happens if
you increment the index value and then fail on the write method? In effect you have lost the data
structure. Another process would not find the tail, and would create a new index and data structure.
You would lose your current entries or, depending on the application, logic could retrieve the
entries in erroneous situations.
131
Transactions enable you to group the take and write methods under a single operation. If the
transaction aborts, any entries taken under the transaction are returned to the space (and of course,
any entries written under it are removed), leaving the space as if the operations never occurred.
The following code fragment demonstrates writing entries to JavaSpaces using a transaction
manager.
TransactionManager manager = findTM(registrar);
Transaction.Created leasedTxn = TransactionFactory.create(manager,
Lease.FOREVER);
Lease lease = leasedTxn.lease;
Transaction transaction = leasedTxn.transaction;
Tail template = new Tail();
template.name = "MessageArray";
// perform space operations under a single transaction
// if error occurs all operations reversed
try {
Tail tail = space.takeIfExists(template, transaction,
JavaSpace.NO_WAIT);
if(tail == null) tail = template;
Message msg = new Message();
msg.text = "Testing transactions in space";
for(int i=0; i<100; i++) {
System.out.println("Writing message "+i);
space.write(msg,transaction,Lease.FOREVER);
tail.increment();
}
System.out.println("Writing tail ");
space.write(tail, transaction, Lease.FOREVER);
System.out.println("Commit transaction");
txn.commit();
} catch (...)
JavaSpace Notifications
If you register for a notify under a transaction, you receive notifications of entries that are
written within the transaction and to the space. When the transaction completes (whether it
commits or aborts), all notification registrations under the transaction are withdrawn. If the
transaction commits, the entries remaining in the transaction might result in notifications as a
response to registrations in the space. The entries also become eligible for read and take
operations from the space.
JavaSpaces Transaction Example
Let's begin this example by building a utility class for service lookup. I'll expand on this utility in
the next chapter when we discuss helper services. For now, you have two services that you need to
find and access: JavaSpaces (outrigger), and the transaction service (mahalo). Our
ServiceFinder utility takes an LUS as a parameter to the methods findSpace and
findTM. Next you create a template that will match any service supporting the interface—
JavaSpace and TransactionManager.
Listing 6.5 ServiceFinder Class
132
package org.jworkplace.util;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.space.JavaSpace;
public class ServiceFinder {
public static JavaSpace findSpace(ServiceRegistrar registrar) {
JavaSpace space = null;
ServiceTemplate template;
Class[] spaceInterface = { JavaSpace.class } ;
template = new ServiceTemplate(null, spaceInterface, null);
try {
space = (JavaSpace)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return space;
}
public static TransactionManager findTM(ServiceRegistrar registrar)
{
TransactionManager txm = null;
ServiceTemplate template;
Class[] transactionManagerInterface =
{ TransactionManager.class } ;
template = new ServiceTemplate(null,
transactionManagerInterface, null);
try {
txm = (TransactionManager)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return txm;
}
}
The TxnClient class demonstrates how to create a transaction and pass it as a parameter to
space operations.
Listing 6.6 The TxnClient Class
package org.jworkplace.client;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import
import
import
import
import
import
net.jini.core.lease.*;
net.jini.core.lookup.ServiceItem;
net.jini.core.lookup.ServiceTemplate;
net.jini.core.lookup.ServiceMatches;
net.jini.core.lookup.ServiceRegistrar;
net.jini.core.discovery.LookupLocator;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
133
import
import
import
import
import
import
import
import
import
import
net.jini.core.transaction.Transaction;
net.jini.core.transaction.TransactionFactory;
net.jini.core.transaction.Transaction.Created;
net.jini.core.transaction.server.ServerTransaction;
net.jini.core.transaction.server.TransactionConstants;
net.jini.core.transaction.server.TransactionManager.Created;
net.jini.core.transaction.server.TransactionManager;
net.jini.core.transaction.server.TransactionParticipant;
net.jini.core.transaction.UnknownTransactionException;
net.jini.core.transaction.CannotAbortException;
import
import
import
import
net.jini.discovery.DiscoveryEvent;
net.jini.discovery.DiscoveryListener;
net.jini.discovery.DiscoveryManagement;
net.jini.discovery.LookupDiscovery;
import net.jini.space.JavaSpace;
import org.jworkplace.util.ServiceFinder;
import org.jworkplace.Message;
public class TxnClient {
private
private
private
private
private
JavaSpace space;
TransactionManager manager;
Transaction.Created leasedTxn;
Transaction txn;
Lease lease;
public TxnClient(ServiceRegistrar registrar){
// use our ServiceFinder to access outrigger and mahalo
manager = ServiceFinder.findTM(registrar);
space = ServiceFinder.findSpace(registrar);
try {
// pass the transaction manager to the factory to
create a
// leased Transaction.Created
leasedTxn = TransactionFactory.create(manager,
Lease.FOREVER);
// get the lease and transaction object
lease = leasedTxn.lease;
txn = leasedTxn.transaction;
// set the lease period for 10 seconds
lease.renew(1000*10);
}
}
}
catch (LeaseDeniedException leaseDenied) {
leaseDenied.printStackTrace();
System.exit(1);
catch (UnknownLeaseException unknownLease) {
unknownLease.printStackTrace();
System.exit(1);
catch (RemoteException remoteException) {
remoteException.printStackTrace();
System.exit(1);
}
// create a Message entry with some text
134
Message msg = new Message();
msg.text = "Testing transactions in space";
try {
// write the entry 100 times passing the transaction
object
for(int i=0; i<100; i++) {
System.out.println("Writing to space message " + i);
space.write(msg,txn,1000*10);
System.out.println( "Lease remaining (millis): " +
(lease.getExpiration() System.currentTimeMillis()));
}
// commit the transaction to make it visible to other
processes
System.out.println("Commit transaction");
txn.commit();
}
catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
try {
txn.abort();
} catch (UnknownTransactionException unknown) {
unknown.printStackTrace();
} catch (CannotAbortException cannotAbort) {
cannotAbort.printStackTrace();
} catch (RemoteException remoteException) {
remoteException.printStackTrace();
}
}
}
public static void main(String args[]) throws Exception {
if(args.length < 1)
{
System.out.println("usage jini://hostname:port");
System.exit(1);
}
// set the security manager
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
// create a LookupLocator for unicast discovery
LookupLocator lookup = new LookupLocator(args[0]);
// find the LUS
ServiceRegistrar registrar = lookup.getRegistrar();
if(registrar != null)
new TxnClient(registrar);
}
}
Compile the Files
135
To build the example, change to the Chapter 6 Example 1 directory and invoke the compile
command:
C:\JiniJavaSpaces\chapter6\example1\compile
The mahalo.jar file contains the transaction support classes for Jini. The compile.cmd script
includes this jar file on the classpath.
Start the TxnClient
Ensure that both the mahalo transaction service and the outrigger JavaSpaces service are running.
Then invoke the startClient.cmd from the Chapter 6 Example 1 directory. You must set
the first parameter on the command line to the hostname and port of the running lookup service.
C:\JiniJavaSpaces\chapter6\example1\startClient jini://hostname:port
Here you simply write 100 messages within a single transaction. You might want to change the
lease time to a low value on the transaction. For instance, the line
lease.renew(1000*10);
is changed to
lease.renew(1000*2);
Unless the transaction commits in less than 2 seconds, an exception is thrown when JavaSpaces
tries to join the transaction. The transaction object does not have a lease manager renewing its
lease, and therefore the transaction manager aborts the transaction. All entries written to the space
are removed.
Transactional Shell Example
The JavaSpaces service is a transaction participant, because it implements the
TransactionParticipant interface and takes a Transaction object as a parameter
in methods exposed by its interface. In Chapter 4, "Building on Jini Foundation Concepts," we
built a JiniShell that enabled us to enter commands and have them run remotely. We can
extend that concept to create a transactional shell. In other words, create a shell that is capable of
participating in transactions, and therefore affect the outcome of the command's success or failure
by voting to commit or abort the transaction. Figure 6.3 depicts the message exchange required to
establish a transaction and participate in the two-phase commit protocol.
Figure 6.3. This scenario diagram outlines the messages exchanged between
participants within the transactional shell example.
136
You extend the Shell interface to support an executeCommand method that takes a
Transaction object as a parameter.
Listing 6.7 TxnShell Interface
package org.jworkplace.command;
import java.rmi.RemoteException;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionException;
public interface TxnShell extends Shell {
public Object executeCommand(Command cmd, Transaction txn)
throws RemoteException, TransactionException;
}
We now define a ParticipantShell service that extends UnicastRemoteObject
and implements TxnShell and TransactionParticipant.
Listing 6.8 ParticipantShell Class
package org.jworkplace.client;
import java.io.IOException;
import java.io.Serializable;
import java.util.Vector;
import
import
import
import
import
import
import
java.rmi.RemoteException;
java.rmi.RMISecurityManager;
java.rmi.server.UnicastRemoteObject;
net.jini.core.transaction.Transaction;
net.jini.core.transaction.TransactionFactory;
net.jini.core.transaction.Transaction.Created;
net.jini.core.transaction.TransactionException;
import
import
import
import
net.jini.core.transaction.server.ServerTransaction;
net.jini.core.transaction.server.TransactionConstants;
net.jini.core.transaction.server.TransactionManager.Created;
net.jini.core.transaction.server.TransactionManager;
137
import
import
import
import
import
net.jini.core.transaction.server.TransactionParticipant;
net.jini.core.transaction.server.CrashCountException;
net.jini.core.transaction.UnknownTransactionException;
net.jini.core.transaction.CannotAbortException;
net.jini.core.transaction.CannotJoinException;
import org.jworkplace.command.*;
public class ParticipantShell extends UnicastRemoteObject implements
TxnShell, TransactionParticipant {
private int crashCount = 0;
public ParticipantShell() throws RemoteException {
super();
// recover log file (crash count and any state needed to
recover)
// take snapshot which would log our crash count to safe
storage
}
public Object executeCommand(Command cmd) throws RemoteException
{
return cmd.execute();
}
public Object executeCommand(Command cmd, Transaction txn) throws
RemoteException, CannotJoinException,
UnknownTransactionException,
CrashCountException
{
// We join the transaction in this method. This assumes
every
// request creates a new transaction.
if(txn != null) {
ServerTransaction serverTxn = (ServerTransaction)txn;
serverTxn.join(this, crashCount);
}
return cmd.execute();
}
// Implementation of the TransactionParticipant interface
public void abort(TransactionManager mgr, long transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "abort" );
// rollback any actions here
// write log
}
public void commit(TransactionManager mgr, long transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "commit" );
// roll forward any actions here
// write log
}
public int prepare(TransactionManager mgr, long transactionID)
138
throws UnknownTransactionException, RemoteException {
System.out.println( "prepare" );
return PREPARED;
// take snapshot here
}
public int prepareAndCommit(TransactionManager mgr, long
transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "prepare & commit" );
// take snapshot here
int result = prepare( mgr, transactionID );
if ( result == PREPARED ) {
commit( mgr, transactionID );
result = COMMITTED;
// roll forward any actions here
// write log
}
return result;
}
}
The Participant class is used as a service helper to provide general Jini housekeeping, such
as discovery and join processing for the TxnShell.
Listing 6.9 Participant Class
package org.jworkplace.client;
import java.io.IOException;
import java.io.Serializable;
import java.util.Vector;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import
import
import
import
import
net.jini.core.lease.*;
net.jini.core.lookup.ServiceItem;
net.jini.core.lookup.ServiceTemplate;
net.jini.core.lookup.ServiceMatches;
net.jini.core.lookup.ServiceRegistrar;
import
import
import
import
import
import
import
import
import
import
import
net.jini.discovery.DiscoveryEvent;
net.jini.discovery.DiscoveryListener;
net.jini.discovery.DiscoveryManagement;
net.jini.discovery.LookupDiscovery;
net.jini.discovery.DiscoveryGroupManagement;
net.jini.discovery.LookupDiscoveryManager;
net.jini.core.discovery.LookupLocator;
net.jini.core.lookup.ServiceID;
net.jini.lookup.JoinManager;
net.jini.lookup.ServiceIDListener;
net.jini.space.JavaSpace;
import org.jworkplace.util.SpaceAccessor;
import org.jworkplace.util.ServiceFinder;
import org.jworkplace.command.*;
139
public class Participant {
// The LookupDiscoveryManager we are using to find lookups
private LookupDiscoveryManager lookupDiscMgr;
private LookupLocator locators[];
private String groups[] = DiscoveryGroupManagement.ALL_GROUPS;
/** Manager for joining lookup services */
private JoinManager joiner = null;
/** ServiceID returned from the lookup registration process */
private ServiceID serviceID = null;
public Participant(String[] args) throws IOException {
init();
}
private void init() throws IOException {
try {
lookupDiscMgr = new LookupDiscoveryManager(groups,
locators, null);
} catch (IOException e) {
System.err.println("JoinState:Problem starting
discovery");
e.printStackTrace();
throw new IOException("JoinState: Problem starting
discovery:"
+ e.getLocalizedMessage());
}
// This is the object we register with the lookup service
TxnShell shell = new ParticipantShell();
/* Register this service with any configured lookup services
*/
if (serviceID == null) {
// First instance ... need service
id
joiner = new JoinManager(
shell,
null,
new SrvcIDListener(),
lookupDiscMgr,
// service object
// service attributes
// ServiceIDListener
// DiscoveryManagement -
null);
// LeaseRenewalManager -
default
default
} else { // Rejoin with (recovered) state information
joiner = new JoinManager(
shell,
// service object
null,
// service attributes
serviceID,
// Service ID
lookupDiscMgr,
// DiscoveryManagement default
null);
// LeaseRenewalManager -
default
}
}
140
public static void main(String args[]) throws Exception {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
new Participant(args);
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
try {
Thread.sleep(100000L);
} catch(InterruptedException e) {
}
System.exit(0);
}
/**
* Utility method for setting the service's ID obtained from
* the lookup registration process.
*/
private void setServiceID(ServiceID id) {
serviceID = id;
}
/**
* Class which handles the callback of a service ID assignment
* by the JoinManager
*/
private class SrvcIDListener implements ServiceIDListener
{
public SrvcIDListener() {
super();
}
/**
* The JoinManager will invoke this method when it receives a
* valid ServiceID from a lookup service.
*/
public void serviceIDNotify(ServiceID id) {
// Set the ID
setServiceID(id);
System.out.println("Received service id");
// Log this event
}
} //end class SrvcIDListener
}
The transaction client can also be a participant by implementing the
TransactionParticipant interface, and joining the transaction.
Listing 6.10 TxnClient Class
package org.jworkplace.client;
import java.io.IOException;
141
import java.io.Serializable;
import java.util.Vector;
import
import
import
import
import
import
net.jini.core.lease.*;
net.jini.core.lookup.ServiceItem;
net.jini.core.lookup.ServiceTemplate;
net.jini.core.lookup.ServiceMatches;
net.jini.core.lookup.ServiceRegistrar;
net.jini.core.discovery.LookupLocator;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import
import
import
import
import
import
import
import
import
import
net.jini.core.transaction.Transaction;
net.jini.core.transaction.TransactionFactory;
net.jini.core.transaction.Transaction.Created;
net.jini.core.transaction.server.ServerTransaction;
net.jini.core.transaction.server.TransactionConstants;
net.jini.core.transaction.server.TransactionManager.Created;
net.jini.core.transaction.server.TransactionManager;
net.jini.core.transaction.server.TransactionParticipant;
net.jini.core.transaction.UnknownTransactionException;
net.jini.core.transaction.CannotAbortException;
import
import
import
import
import
net.jini.space.JavaSpace;
org.jworkplace.util.ServiceFinder;
org.jworkplace.Message;
org.jworkplace.command.TxnShell;
org.jworkplace.client.FileList;
public class TxnClient extends Thread implements
TransactionParticipant {
private
private
private
private
private
private
private
TransactionManager manager;
Transaction.Created leasedTxn;
Transaction txn;
Lease lease;
static String directory;
ServiceRegistrar registrar;
JavaSpace space;
public TxnClient(ServiceRegistrar registrar) {
this.registrar = registrar;
}
public static void main(String args[]) throws Exception {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
LookupLocator lookup = new LookupLocator(args[0]);
ServiceRegistrar registrar = lookup.getRegistrar();
directory = args[1];
new TxnClient(registrar).start();
}
public void run() {
142
manager = ServiceFinder.findTM(registrar);
space = ServiceFinder.findSpace(registrar);
try {
leasedTxn = TransactionFactory.create(manager,
Lease.FOREVER);
lease = leasedTxn.lease;
txn = leasedTxn.transaction;
System.out.println( "Lease duration (millis): " +
(lease.getExpiration() System.currentTimeMillis()));
lease.renew(1000*10);
System.out.println( "Lease duration (millis): " +
(lease.getExpiration() System.currentTimeMillis()));
}
}
}
catch (LeaseDeniedException leaseDenied) {
leaseDenied.printStackTrace();
System.exit(1);
catch (UnknownLeaseException unknownLease) {
unknownLease.printStackTrace();
System.exit(1);
catch (RemoteException remoteException) {
remoteException.printStackTrace();
System.exit(1);
}
// Export our client to participate in the transaction
try {
UnicastRemoteObject.exportObject(this);
} catch (RemoteException remoteException) {
remoteException.printStackTrace();
System.exit(1);
}
try {
// cast the transaction object to ServerTransaction
ServerTransaction st = (ServerTransaction)txn;
// join the transaction
st.join( this, 0 );
TxnShell shell = null;
Class[] shelltypes = {
TxnShell.class } ;
ServiceTemplate template = new ServiceTemplate(null,
shelltypes, null);
// Find the TxnShell
shell = (TxnShell)registrar.lookup(template);
// invoke the command passing the transaction object
String[] files = (String [])shell.executeCommand(
new
FileList(directory), txn);
if(files == null) {
System.out.println("Invalid directory");
} else {
for(int i=0; i<files.length; i++)
System.out.println(files[i]);
}
// write a message to space using the transaction
143
Message msg = new Message();
msg.text = directory;
space.write(msg,txn,1000*10);
// now commit it all
txn.commit();
}
catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
try {
txn.abort();
} catch (UnknownTransactionException unknown) {
unknown.printStackTrace();
} catch (CannotAbortException cannotAbort) {
cannotAbort.printStackTrace();
} catch (RemoteException remoteException) {
remoteException.printStackTrace();
}
}
}
// if an error occurs this method is called
public void abort(TransactionManager mgr, long transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "-------abort--------" );
}
// now roll forward the transaction
public void commit(TransactionManager mgr, long transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "---------commit-------" );
}
// save any state information required
public int prepare(TransactionManager mgr, long transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "--------prepare--------" );
return PREPARED;
}
// if the only participant in the transaction
public int prepareAndCommit(TransactionManager mgr, long
transactionID)
throws UnknownTransactionException, RemoteException {
System.out.println( "---------prepare & commit---------" );
int result = prepare( mgr, transactionID );
if ( result == PREPARED ) {
commit( mgr, transactionID );
result = COMMITTED;
}
return result;
}
}
Compile the Files
To build the second example in this chapter, change to the Chapter 6 Example 2 directory and
invoke the build command:
144
C:\JiniJavaSpaces\chapter6\example2\build
Create the Required Stub Interfaces
This will compile the files and invoke the rmic compiler to generate the stub files for the
TxnShell class and the ParticipantShell class.
Create Jar Files (Client-Side and Server-Side)
It also creates the client-side and server-side jar files. Copy the client -dl.jar file to the client
HTTP directory. Copy the service -dl.jar file to the server HTTP directory.
Start the ParticipantShell
Go to the service directory. Start the ParticipantShell command processor by invoking
the participant script.
C:\JiniJavaSpaces\Chapter6\example2\service>startParticipant
Start the TxnClient
To test the transaction client, move to the directory and machine where you deployed the client
software. Run the following client command:
C:\JiniJavaSpaces\Chapter6\example2\client>startClient
jini://hostname directory
where hostname is the host and port (default to 4160) running the LUS where the space is
registered; and directory is the remote directory location of the files to list.
Figure 6.4 demonstrates the ParticipantShell and TxnClient invoking a
FileList command.
Figure 6.4. You should see the prepare and commit messages in both windows.
145
Both the ParticipantShell and TxnClient receive the appropriate prepare and
commit messages in support of the two-phase commit protocol. It is in these methods that
services would store and restore the necessary application state to recover from failures or roll
forward changes to provide the appropriate ACID property guarantees.
As you build applications in Part III, you will log the necessary state information such as crash
count and service ID to make the applications more resilient and production-ready.
Summary
Transactional systems let you group together a set of operations so that either all the operations
complete or none of them do. Jini supports the two-phase commit protocol, which is a standard
protocol for coordinating participants in a distributed transaction. While Jini defines a standard
transaction interface, it leaves the implementation to the service provider. There is, however, a
sample implementation called mahalo provided by Sun Microsystems.
The JavaSpaces service requires the transaction service. The first example in this chapter
demonstrated how to coordinate JavaSpace operations under the control of a transaction.
The next example expounded on the concepts of a remote Shell that were introduced in Chapter 4.
It demonstrates the two-phase commit protocol, by implementing the Participant interface.
Jini's support for transactions provides a key component to building more robust applications and
services.
146
Chapter 7. The Helper Services
IN THIS CHAPTER
•
•
•
•
•
•
Overview of Helper Services
The Disconnected Client or Service Entity
The LDS Interface Explained
The LRS Interface Explained
The EMS Interface Explained
Summary
This chapter provides an overview of the Jini 1.1 Helper services and how they can be used to
complement core and user-defined services.
Overview of Helper Services
To help simplify the process of developing clients and services for the Jini environment, several
helper services have been defined. The Jini specifications define two categories of helper entities:
helper utilities and helper services. These utilities and services provide a basis for building
applications that demonstrate desirable behavior in a Jini community.
Helper utilities are programming components that can be used during the construction of Jini
services and/or clients. Helper utilities are not remote services and do not register with a lookup
service (LUS). Helper utilities are instantiated locally by entities wishing to employ them. Helper
utilities include the following:
•
•
•
•
LookupDiscovery
LeaseRenewalManager
JoinManager
ServiceDiscoveryManager
A helper service is a Jini service that can be registered with any number of lookup services and
whose methods can execute on remote hosts. In general, a helper service should be of use to more
than one type of entity participating in the Jini application environment, and should provide a
significant reduction in development complexity for developers of such entities. A helper service
consists of an interface or set of interfaces and an associated implementation encapsulating
behavior that is either required or highly desirable in services that adhere to the Jini technology
programming model. Helper services include the following:
•
•
•
Lookup Discovery Service
Lease Renewal Service
Event Mailbox Service
Lookup Discovery Service
The Lookup Discovery Service (LDS) is a helper service that uses the Jini discovery protocols to
find LUSes in which an entity (a client or service) has expressed interest, and to notify the entity
when a previously unavailable LUS becomes available (see Figure 7.1).
147
Figure 7.1. The LDS monitors LUS changes in the Jini community and notifies
entities that have registered for notifications.
Why would an entity want to delegate this responsibility? An activatable service that deactivates
may want to employ the LDS to perform discovery duties on the entity's behalf. Such an entity
may want to deactivate for various reasons, one being to conserve resources. While the entity is
deactivated, the LDS running on the same or a separate host would employ the discovery
protocols to find LUSes in which the entity has expressed interest, and would notify the entity
when a previously unavailable LUS becomes available, when the entity has reactivated again.
Lease Renewal Service
The Lease Renewal Service (LRS) is a helper service that can be employed by both Jini clients
and services to perform all lease renewal duties on their behalf.
An entity would delegate this responsibility so that services wishing to remain inactive until they
are needed can request that the lease renewal service take on the responsibility of renewing the
leases granted to the service (see Figure 7.2). This enables the service to be dormant without
risking the loss of access to the resources corresponding to the leases being renewed.
Figure 7.2. The LRS can provide lease management for disconnected clients.
148
Entities that have continuous access to a network but cannot be continuously connected to that
network (for example, a cell phone), might also find this service useful. By enabling an LRS
(which can be continuously connected) to renew the leases on the resources acquired by the entity,
the entity may remain disconnected until needed. This lease renewal service removes the need to
perform the discovery and lookup process each time the entity reconnects to the network,
potentially resulting in a significant increase in efficiency.
Event Mailbox Service
The Event Mailbox Service (EMS) is a helper service that can be employed by entities to store
event notifications on their behalf. When an entity registers with the event mailbox service, that
service will collect events intended for the registered entity until the entity initiates delivery of the
events (see Figure 7.3).
Figure 7.3. The EMS can ensure that events targeted at a disconnected client are
available when the client reactivates. The EMS can provide store-and-forward
semantics.
A service such as the EMS can be useful to entities that desire more control over the delivery of
the events sent to them. Some entities operating in a distributed system may find it undesirable or
149
inefficient to be contacted solely for the purpose of having an event delivered, preferring to defer
the delivery to a time that is more convenient as determined by the entity itself.
For example, an entity wishing to deactivate or detach from a network may wish to have its events
stored until it is available to retrieve them. Additionally, some entities may want to batch process
event notifications for efficiency.
The Disconnected Client or Service Entity
The general theme in the usage patterns mentioned previously is support for disconnected entities,
and entities or environments where computational conservation is of paramount importance.
Delegating Responsibilities
By using helper services, entities can disconnect from the network and still have their Jini "good
citizenship" behavior managed.
For clients, this might involve renewing leases to services or storing events for later consumption.
For services, the responsibilities also include LUS residency to maintain a persistent state across
service restarts and crashes. This persistent state consists of the following:
•
•
•
•
The service must use the service ID obtained from the first LUS it registered with across
all subsequent registrations.
Attributes associated with the service in LUSes. These attributes must be consistent for all
LUS entries.
Groups the service wishes to participate in.
Specific LUSes to register with.
When a service deactivates, this information needs to be saved to persistent storage. Likewise,
when a service activates, it must be retrieved to initialize the service. Other pieces of information
specific to a service implementation might also need to be saved.
Resource Conservation
The helper services also provide entities with an environment that can reduce the overall resource
requirements. If many fine-grained services must run concurrently, sharing the services offered by
the helper services can be advantageous. Also, if the physical limitations of the device, such as
memory, constrain the supporting environment, delegating responsibility to helper services on the
network can reduce the demands on the resource-constrained device.
Let's look at the helper services in more detail.
The LDS Interface Explained
The LookupDiscoveryService interface defines the lookup discovery helper service.
Through that interface, Jini services and Jini clients may request that the discovery process be
performed on their behalf.
Listing 7.1 The LookupDiscoveryService Interface
150
public interface LookupDiscoveryService
{
public abstract LookupDiscoveryRegistration register(String[]
groups,
LookupLocator[]
locators,
RemoteEventListener
listener,
MarshalledObject
handback,
long leaseDuration)
throws
RemoteException;
}
Clients find the discovery service by creating a ServiceTemplate using the
LookupDiscovery Service interface as a parameter.
public LookupDiscoveryService findLDS(ServiceRegistrar registrar) {
LookupDiscoveryService lookupService;
ServiceTemplate template;
// set the interface class
Class[] lookupDiscoveryServiceInterface = {
LookupDiscoveryService.class } ;
// create the template
template = new ServiceTemplate(null,
lookupDiscoveryServiceInterface,
null);
// use the already found LUS to find the LDS
try {
lookupService =
(LookupDiscoveryService)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return lookupService;
}
The LookupDiscoveryService interface defines a single register method. An entity
uses the interface to define the LUSes of interest to the entity.
// The lookup registration returned by the LDS
LookupDiscoveryRegistration lookupRegistration;
// The registration handback information
MarshalledObject data = handBackData;
// The lease on the LDS registration
long leaseTime = 1000*60*5; // 5 minutes
// Discover all groups using no specific locators
lookupRegistration =
lookupService.register(LookupDiscovery.ALL_GROUPS,
new LookupLocator[0],
new LDSListener(),
data,
leaseTime);
151
The register method also requires:
•
•
•
A RemoteEventListener that will be notified when the discovery service adds or
removes an LUS of interest, for example LDSListener.
A MarshalledObject that will be available to the using entity through event
notification, for example data.
A requested lease time to indicate how long the LDS should remain active for the using
entity, for example leaseTime.
The using entity receives a LookupDiscoveryRegistration interface to manage the
registration with LookupDiscoveryService. With the
LookupDiscoveryRegistration interface, an entity can configure its registration
requirements with the discovery service. For instance, you can change and remove the groups and
locators of interest in your LUS view:
Listing 7.2 The LookupDiscoveryRegistration Interface
package net.jini.discovery;
public interface LookupDiscoveryRegistration {
public EventRegistration getEventRegistration();
public Lease getLease();
public ServiceRegistrar[] getRegistrars()
throws LookupUnmarshalException,
RemoteException;
public String[] getGroups() throws RemoteException;
public LookupLocator[] getLocators()
throws RemoteException;
public void addGroups(String[] groups)
throws RemoteException;
public void setGroups(String[] groups)
throws RemoteException;
public void removeGroups(String[] groups)
throws RemoteException;
public void addLocators(LookupLocator[] locators)
throws RemoteException;
public void setLocators(LookupLocator[] locators)
throws RemoteException;
public void removeLocators(LookupLocator[] locators)
throws RemoteException;
public void discard(ServiceRegistrar registrar)
throws RemoteException;
}
When the LDS discovers or discards an LUS matching the criteria established through one of its
registrations, the LDS sends an instance of the RemoteDiscoveryEvent class to the
RemoteEventListener implemented by the entity and registered with the LDS.
Listing 7.3 The RemoteDiscoveryEvent Class
package net.jini.discovery;
public class RemoteDiscoveryEvent extends RemoteEvent {
public RemoteDiscoveryEvent(Object source,
long eventID,
long seqNum,
MarshalledObject handback,
152
boolean discarded,
Map groups)
throws IOException { ...}
public boolean isDiscarded() { ...}
public ServiceRegistrar[] getRegistrars()
throws LookupUnmarshalException { ...}
public Map getGroups() { ...}
}
The RemoteDiscoveryEvent class provides an encapsulation of event information that the
LDS uses to notify a registration of the occurrence of an event involving one or more Service
Registrar objects (LUSes) in which the entity has registered interest. The LDS passes an
instance of this class to the registration's discovery listener when one of the following events
occurs:
•
Each LUS referenced in the event has been discovered for the first time, or has been
rediscovered after having been discarded.
Each LUS referenced in the event has been either actively or passively discarded.
•
RemoteDiscoveryEvent is a subclass of RemoteEvent, adding the following
additional items:
•
A boolean indicating whether the LUSes referenced by the event have been
discovered or discarded.
A set of marshalled instances of the ServiceRegistrar interface representing
proxies of the discovered or discarded LUSes referenced by the event.
A Map instance in which the elements of the map's key set are the instances of
ServiceID that correspond to each LUS reference returned in the event; and in which
the map's value set contains the corresponding member groups of each LUS reference.
•
•
Methods are defined through which this additional state can be retrieved upon receipt of an
instance of this class.
The following example provides a template LDS listener.
Listing 7.4 The LDSListener Class
package org.jworkplace.utils;
import
import
import
import
import
import
import
import
import
java.rmi.MarshalledObject;
java.rmi.server.UnicastRemoteObject;
java.rmi.RemoteException;
net.jini.core.lookup.ServiceRegistrar;
net.jini.core.event.RemoteEventListener;
net.jini.core.event.RemoteEvent;
net.jini.core.event.UnknownEventException;
net.jini.discovery.RemoteDiscoveryEvent;
net.jini.discovery.LookupUnmarshalException;
public class LDSListener extends UnicastRemoteObject implements
RemoteEventListener {
public LDSListener() throws RemoteException {
super();
}
public void notify(RemoteEvent event)throws UnknownEventException,
153
RemoteException
{
// The LDS generates a RemoteDiscoveryEvent
RemoteDiscoveryEvent evt = (RemoteDiscoveryEvent)event;
// Appropriate logic could be inserted here
// Just dump some event info to the console
try {
// The ID is unique per instance of LDS
System.out.println("ID: " + evt.getID());
// The sequence number can be used to determine gaps
// or missing notifications, the sequence number increments
System.out.println("Sequence #: " + evt.getSequenceNumber());
// a handback object can be provided at registration
// that will be returned unmodified
MarshalledObject object = event.getRegistrationObject();
if(object != null) {
System.out.println("LDS registration object: " +
object.get());
} else {
System.out.println("No LDS registration object set");
}
// isDiscarded can by used to determine if this is a
// discovered or discarded LUS notification
System.out.println("Discovered : "+ evt.isDiscarded());
if (! evt.isDiscarded()) {
ServiceRegistrar[] registrars = null;
try {
// get the registrars that have been discovered
registrars = evt.getRegistrars();
} catch(LookupUnmarshalException e) {
e.printStackTrace();
return;
}
// Simply list the discovered LUSs
for (int i = 0; i < registrars.length; i++) {
System.out.println(registrars[i]);
}
}
} catch (Exception e) {
e.printStackTrace(); }
}
}
This listener extends UnicastRemoteObject, which means that the listener must be
manually started and remain active to receive remote event notification. This might not provide
the most robust alternative for listener notification.
You will develop a framework for service management later in this chapter that will build on
activatable services. The benefit of the framework is that services can rely on the activation
mechanism of RMI and only be active when required, for example when a notification needs to be
processed. Building the environment in this manner will provide a more robust environment, and
help to reduce the overall resource requirements.
154
The Fiddler Implementation
Sun Microsystems provides a reference implementation of the Jini LDS called fiddler. You can
run fiddler as an activatable service or as a transient service.
The version you use will depend on your requirements for persistence. In other words, will you
require discovery service state to survive restarts or system crashes? Of course, the persistent or
activatable service version will also require more system resources—nothing is free!
Starting Fiddler
The following script can be used to start the activatable version of fiddler:
Listing 7.5 The startFidler.cmd Script
@echo off
@call setup.cmd
set GROUPS=public
echo
echo
echo
echo
echo
echo
echo
echo
Jini and JavaSpaces Application Development
------------------------------------------Jini install directory
%JINI_HOME%
Examples install directory
%JWORK_HOME%
Web server
%HTTP_ADDRESS%
Default group
%GROUPS%
------------------------------------------Starting the fiddler lookup discovery service...
java -jar Djava.security.policy=%JINI_HOME%\example\lookup\policy.all
%JINI_HOME%\lib\fiddler.jar http://%HTTP_ADDRESS%/fiddler-dl.jar
%JINI_HOME%\example\lookup\policy.all
%JWORK_HOME%\services\logs\fiddler_log %GROUPS%
And the following command can be used to start the transient version of fiddler:
java -cp %JINI_HOME%\lib\fiddler.jar Djava.rmi.server.codebase=http:\\hostname:8080/
fiddler-dl.jar -Djava.security.policy=%JINI_HOME%\policy\policy.all
com.sun.jini.fiddler.TransientFiddler
%JWORK_HOME%\services\logs\transient-fiddler_log
public
The discovery facility of the LDS, together with its event mechanism, are the resources that
entities can register to use. Because the resources of the LDS are leased, access is granted for only
a limited period of time, unless the lease is extended . So, the astute reader will determine that if
an entity disconnects, it will still need to renew its lease with the LDS.
The LRS Interface Explained
According to the Jini Lease Renewal Service Specification (see Appendix C),
155
The client of a leased service may run into difficulties if that service deactivates. Unless the client
ensures that some other process renews the client's leases while it is inactive, or that the client is
activated before its lease begins to expire, the client will lose access to the resources it has
acquired. This loss can be particularly dramatic in the case of lookup service registrations. A
service's registration with a lookup service is leased—if the service deactivates… and it does not
take appropriate steps, its registrations with lookup services will expire, and before long it will be
inaccessible. If that service only becomes active when clients invoke its methods, it may never
become active again, because at this point new clients may not be able to find it.
The Lease Renewal Service provides the interface to enable using entities to delegate their lease
management responsibilities to a third party. The LRS is defined by the
LeaseRenewalService interface.
Listing 7.6 The LeaseRenewalService Interface
public interface LeaseRenewalService
{
public LeaseRenewalSet createLeaseRenewalSet (long leaseDuration)
throws RemoteException;
}
A client entity invokes the createLeaseRenewalSet method to create a set to which the
entity will add leases that the renewal service should renew.
The client entity leases a renewal set from the LRS for a specific amount of time
(leaseDuration). When the lease expires, the LRS stops renewing the leases in that set. This
is important because the set is also a leased resource. If you want the leases within the set to
continue to be renewed, you must renew the container, for example the set.
After a client entity becomes inactive, how can it renew its lease for the LeaseRenewalSet?
The client entity can ask the LeaseRenewalSet implementation to send it a warning event
before the lease expires; it can then activate and renew the set's lease. The setExpiration
WarningListener method, of the LeaseRenewalSet class, registers the warning
request.
Listing 7.7 The LeaseRenewalSet Interface
package net.jini.lease;
public interface LeaseRenewalSet {
final public static long RENEWAL_FAILURE_EVENT_ID = 0;
final public static long EXPIRATION_WARNING_EVENT_ID = 1;
public void renewFor(Lease leaseToRenew,
long desiredDuration,
long renewDuration)
throws RemoteException;
public void renewFor(Lease leaseToRenew,
long desiredDuration)
throws RemoteException;
public EventRegistration setExpirationWarningListener(
RemoteEventListener listener,
long
minWarning,
MarshalledObject
handback)
throws RemoteException;
156
public void clearExpirationWarningListener()
throws RemoteException;
public EventRegistration setRenewalFailureListener(
RemoteEventListener listener,
MarshalledObject
handback)
throws RemoteException;
public void clearRenewalFailureListener()
throws RemoteException;
public Lease remove(Lease leaseToRemove)
throws RemoteException;
public Lease[] getLeases()
throws LeaseUnmarshalException, RemoteException;
public Lease getRenewalSetLease();
}
Clients find the renewal service by creating a ServiceTemplate, using the LRS interface as
a parameter.
public LeaseRenewalService findLRS(ServiceRegistrar registrar) {
LeaseRenewalService leaseService;
ServiceTemplate template;
// set the interface class
Class[] leaseRenewalServiceInterface = {
LeaseRenewalService.class } ;
// create the template
template = new ServiceTemplate(null,
leaseRenewalServiceInterface,
null);
// use the already discovered LUS to find the LRS
try {
leaseService =
(LeaseRenewalService)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return leaseService;
}
The following code fragment creates a LeaseRenewalSet and requests a one-hour lease
duration, with notification one minute prior to expiration.
// lease the set for 1 hour
long leaseDuration = 1000*60*60;
// notify one minute before expiration
long leaseNotification = 1000*60*1;
leaseRenewalSet =
leaseRenewalService.createLeaseRenewalSet(leaseDuration);
leaseRenewalSet.setExpirationWarningListener(leaseNotification,null,n
ull);
This fragment supplies the notification:
public void notify(RemoteEvent event)throws UnknownEventException,
RemoteException
157
{
ExpirationWarningEvent evt = (ExpirationWarningEvent)event;
// get the set
Lease lease = evt.getRenewalSetLease();
try {
// extend for an additional hour
// notification still set at one minute
lease.renew(leaseDuration);
} catch (Exception e) { e.printStackTrace(); }
}
And adding leases to be managed is accomplished with the following:
Lease lease = someLeasedResource;
leaseRenewalSet.renewFor(lease, someLeaseTime);
You now have a service that can manage all the leases acquired by your application. All that is
required is access to the LeaseRenewalSet.
The Norm Implementation
Sun Microsystems provides a reference implementation of the Jini Lease Renewal Service called
norm. As supplied, you can run norm only as an activatable service.
Starting Norm
The following script can be used to start the activatable version of norm:
Listing 7.8 The startNorm.cmd Script
@echo off
@call setup.cmd
set GROUPS=public
echo
echo
echo
echo
echo
echo
echo
echo
Jini and JavaSpaces Application Development
------------------------------------------Jini install directory
%JINI_HOME%
Examples install directory
%JWORK_HOME%
Web server
%HTTP_ADDRESS%
Default group
%GROUPS%
------------------------------------------Starting the norm lease renewal service...
java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all
%JINI_HOME%\lib\norm.jar
http://%HTTP_ADDRESS%/norm-dl.jar %JINI_HOME%\policy\policy.all
%JWORK_HOME%\services\logs\norm_log %GROUPS%
The LRS permits client entities to disconnect or become inactive and still have a process in place
that can manage resource leasing requirements. As noted previously, this is especially important
for activatable services that must maintain residency in the Jini community even while dormant.
Often the RemoteEventListener that is supplied to the setExpirationWarning
Listener method of the LeaseRenewalSet is also an activatable service. This allows
158
you to activate the listener only when leases are preparing to expire, and renew or cancel leases as
processing dictates.
Let's now look at the final helper service in the Jini distribution, the EventMailbox service,
which is designed to provide delegated remote event notification.
The EMS Interface Explained
A Jini service may choose to delay handling of some remote events directed to it. A dormant
activatable Jini service implementation might not want to activate every time it receives a remote
event. The Event Mailbox Service (EMS) enables a service to delegate event listening to a third
party process. The EMS can be used to intercept events, store them, and forward them when the
mailbox's client deems it convenient.
The EMS is defined by the EventMailbox interface.
Listing 7.9 The EventMailbox Interface
public interface EventMailbox
{
MailboxRegistration register (long leaseDuration)
throws RemoteException,
LeaseDeniedException;
}
Clients find the EMS by creating a ServiceTemplate, using the interface as a parameter.
public EventMailbox findEMS(ServiceRegistrar registrar) {
EventMailbox mailbox;
ServiceTemplate template;
// set the interface class
Class[] eventMailboxInterface = {
EventMailbox.class } ;
// create the template
template = new ServiceTemplate(null,
eventMailboxInterface,
null);
try {
// use an existing LUS to fine the EMS
mailbox = (EventMailbox)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return mailbox;
}
Clients invoke the register method on the EventMailbox and receive a
MailboxRegistration as a result.
Listing 7.10 The MailboxRegistration Interface
package net.jini.event;
159
public interface MailboxRegistration
{
Lease getLease();
RemoteEventListener getListener();
void enableDelivery(RemoteEventListener target)
throws RemoteException;
void disableDelivery() throws RemoteException;
}
With the MailboxRegistration interface, an entity can manage the time and location of
remote event delivery. A client entity retrieves a RemoteEventListener from the
registration object and uses this listener in any method that requires one. The client entity can then
disconnect from the network and not worry about losing events. The events will be directed and
stored by the mailbox listener. At a later point in time, the client entity can activate and invoke the
enableDelivery method, specifying a RemoteEventListener that becomes the
target for the saved events. The client entity can toggle between enabling and disabling delivery
until it terminates or allows its lease to expire with the mailbox service.
The Mercury Implementation
Sun Microsystems provides a reference implementation of the Jini Event Mailbox Service called
mercury. You can run mercury as an activatable service or transient service.
The following script can be used to start the activatable version of mercury.
Listing 7.11 The startMercury.cmd Script
@echo off
@call setup.cmd
set GROUPS=public
echo
echo
echo
echo
echo
echo
echo
echo
Jini and JavaSpaces Application Development
------------------------------------------Jini install directory
%JINI_HOME%
Examples install directory
%JWORK_HOME%
Web server
%HTTP_ADDRESS%
Default group
%GROUPS%
------------------------------------------Starting the mercury event mailbox service...
java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all
%JINI_HOME%\lib\mercury.jar http://%HTTP_ADDRESS%/mercury-dl.jar
%JINI_HOME%\policy\policy.all %JWORK_HOME%\services\logs\mercury_log
%GROUPS%
And the following command can be used to start the transient version of mercury:
java -cp %JINI_HOME%\lib\mercury.jar Djava.rmi.server.codebase=http:\\hostname:8080/
mercury-dl.jar -Djava.security.policy=%JINI_HOME%\policy\policy.all
com.sun.jini.mercury.TransientMercury
%JWORK_HOME%\services\logs\transient-mercury_log
public
A Framework for Service Registration
160
Now that we have looked at the helper services, let's develop a framework for demonstrating their
usage.
You want to enable activatable services in your Jini community. Activatable services require the
following steps:
•
•
•
Create an activation group description.
Create an activation object description.
Register with the activation system.
Create an ActivationGroupDesc that will encapsulate the parameters necessary for the
activation system to start your group of services. The ActivatableService class assists in
defining an activatable service.
Listing 7.12 The ActivatableService Class
package org.jworkplace.util;
import
import
import
import
import
import
import
import
import
import
import
import
import
public
java.rmi.activation.Activatable;
java.rmi.activation.ActivationDesc;
java.rmi.activation.ActivationGroupDesc;
java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
java.rmi.activation.ActivationGroup;
java.rmi.activation.ActivationGroupID;
java.rmi.activation.ActivationException;
java.rmi.activation.UnknownGroupException;
java.rmi.MarshalledObject;
java.rmi.RemoteException;
java.rmi.RMISecurityManager;
java.io.IOException;
java.util.Properties;
class ActivatableService {
// Uniquely identifies the VM for the group
private ActivationGroupID groupID = null;
// Path and command-line arguments used to spawn VM
protected ActivationGroupDesc groupDesc = null;
// the service description required by rmid
protected ActivationDesc objectDesc = null;
// the service we are registering with rmid
protected Object proxy = null;
protected void createGroup(Properties props, CommandEnvironment
env)
{
// create the startup group description
groupDesc = new ActivationGroupDesc(props, env);
try {
// get the unique id for the group
groupID =
ActivationGroup.getSystem().registerGroup(groupDesc);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
161
System.exit(1);
}
// create the activation group
try {
ActivationGroup.createGroup(groupID, groupDesc, 0);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
}
protected void registerObject(ActivationGroupID groupID,
String className,
String codebase,
MarshalledObject data,
boolean restart)
{
// create the startup service description
try {
// service does not belong to an existing group
if(groupID == null) {
objectDesc = new ActivationDesc(className,
codebase,
data,
restart);
// service belongs with an existing group
} else {
objectDesc = new ActivationDesc(groupID,
className,
codebase,
data,
restart);
}
}
catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
// register with the activation system
try {
service = Activatable.register(objectDesc);
} catch(UnknownGroupException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
}
}
}
You can extend your ActivatableService class for each service you define as activatable.
Your ServiceListener (see Listing 7.13), creates and registers an activatable
162
RemoteEvent Listener. It provides the necessary support to register the activatable
listener with the LDS. So, when a new LUS is discovered, the LDS will wake up the listener with
the appropriate notification.
In addition, it finds and uses the LRS to provide an example of the lease renewal support required
for activatable services.
Listing 7.13 The ServiceListener Class
package org.jworkplace.utils;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.io.IOException;
java.util.Properties;
java.rmi.MarshalledObject;
java.rmi.Naming;
java.rmi.RemoteException;
java.rmi.RMISecurityManager;
net.jini.core.discovery.LookupLocator;
net.jini.core.event.RemoteEventListener;
net.jini.core.lookup.ServiceRegistrar;
net.jini.core.lease.Lease;
net.jini.discovery.DiscoveryEvent;
net.jini.discovery.DiscoveryListener;
net.jini.discovery.DiscoveryManagement;
net.jini.discovery.LookupDiscovery;
net.jini.discovery.LookupDiscoveryService;
net.jini.discovery.LookupDiscoveryRegistration;
net.jini.lease.LeaseRenewalService;
net.jini.lease.LeaseRenewalSet;
org.jworkplace.util.*;
public class ServiceListener extends ActivatableService implements
DiscoveryListener
{
// Discovers lookup services
private DiscoveryManagement mgt = null;
// The lookup discovery service
private LookupDiscoveryService lookupService = null;
// the lookup discovery registration
protected LookupDiscoveryRegistration lookupRegistration = null;
// The lease renewal service
private LeaseRenewalService leaseRenewalService = null;
// the lease renewal service lease set used to manage our leases
protected LeaseRenewalSet leaseRenewalSet = null;
// the lease on the lease renewal set
private Lease lookupLease = null;
protected long leaseDuration = 120000L;
protected long leaseNotification = 60000L;
// the codebase for the VM
private String CODEBASE = null;
// the service class name
protected String classname =
"org.jworkplace.service.ServiceListenerProxy";
163
public ServiceListener(String[] args) {
super();
Properties props = new Properties();
// codebase
CODEBASE = args[0];
props.put("java.rmi.server.codebase", args[0]);
// security policy
props.put("java.security.policy", args[1]);
// class path
props.put("java.class.path",
System.getProperty("java.class.path"));
// now create the group description
createGroup(props, null);
}
protected void registerWithActivation() throws IOException {
// register our service listener
// we are registering the ServiceListenerProxy
registerObject(groupID,
classname,
CODEBASE,
getActivationHandback(),
false);
// now start finding registrars (lookup services)
mgt = new LookupDiscovery(LookupDiscovery.NO_GROUPS);
mgt.addDiscoveryListener(this);
((LookupDiscovery)mgt).setGroups(LookupDiscovery.ALL_GROUPS);
}
// implementation of DiscoveryListener
// called when LUS discovered
public synchronized void discovered(DiscoveryEvent de) {
// get the array of lookup services discovered
ServiceRegistrar[] registrars = de.getRegistrars();
// register with LDS
registerWithLDS(registrars, getDiscoveryEventListener());
// register with LRS
// the lookupLease contains the lease on the LDS
registerWithLRS(registrars, getWarningEventListener(),
lookupLease);
}
public void discarded(DiscoveryEvent de) { }
// used to register with LDS
protected synchronized void registerWithLDS(ServiceRegistrar[]
registrars,
RemoteEventListener listener) {
164
// if already registered return
if(lookupRegistration == null) {
for(int i=0; i < registrars.length; i++) {
try {
lookupService = ServiceFinder.findLDS(registrars[i]);
// if found register with it
if(lookupService != null) {
// register to discover all LUSs
// set the expiration warning listener to our
// ServiceListenerProxy
lookupRegistration =
lookupService.register(LookupDiscovery.ALL_GROUPS,
new LookupLocator[0],
listener,
getListenerHandback(),
leaseDuration);
break;
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
// get the LDS lease to renew using the LRS
lookupLease = lookupRegistration.getLease();
}
}
protected synchronized void registerWithLRS(ServiceRegistrar[]
registrars, RemoteEventListener listener, Lease lease)
{
if(leaseRenewalSet == null) {
for(int i=0; i < registrars.length; i++) {
try {
leaseRenewalService =
ServiceFinder.findLRS(registrars[i]);
if(leaseRenewalService != null) {
System.out.println("Found a lease renewal service");
leaseRenewalSet =
leaseRenewalService.createLeaseRenewalSet(leaseDuration);
leaseRenewalSet.renewFor(lease, Lease.FOREVER);
leaseRenewalSet.setExpirationWarningListener(listener,
leaseNotification,
getWarningHandback());
break;
}
} catch(Exception e) { e.printStackTrace(); }
}
}
}
// overrides
protected MarshalledObject getActivationHandback() {
return null;
}
protected MarshalledObject getListenerHandback() {
return null;
}
165
protected MarshalledObject getWarningHandback() {
return null;
}
protected RemoteEventListener getDiscoveryEventListener() {
return (RemoteEventListener)proxy;
}
protected RemoteEventListener getWarningEventListener() {
return (RemoteEventListener)proxy;
}
public static void main(String[] args) {
if(args.length < 2) {
System.out.println("Invalid usage: set codebase and security
policy ");
System.exit(1);
}
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
new ServiceListener(args).registerWithActivation();
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
try {
Thread.sleep(10000L);
} catch(InterruptedException e) {
}
System.exit(0);
}
}
The activatable RemoteEventListener (see Listing 7.14), receives notification of both
LDS events (RemoteDiscoveryEvent) and LRS events
(ExpirationWarningEvent).
Listing 7.14 The ServiceListenerProxy Class
public class ServiceListenerProxy extends Activatable implements
RemoteEventListener
{
private MarshalledObject data;
public ServiceListenerProxy(ActivationID id, MarshalledObject
data)
throws java.rmi.RemoteException
{
super(id,0);
166
this.data = data;
System.out.println("ServiceListenerProxy active...");
}
public void notify(RemoteEvent event)throws UnknownEventException,
RemoteException
{
if(event instanceof RemoteDiscoveryEvent) {
RemoteDiscoveryEvent evt = (RemoteDiscoveryEvent)event;
try {
System.out.println("ID: " + evt.getID());
System.out.println("Sequence #: " +
evt.getSequenceNumber());
MarshalledObject object = event.getRegistrationObject();
if(object != null) {
System.out.println("LDS registration object: " +
object.get());
} else {
System.out.println("No LDS registration object set");
}
System.out.println("Discovered : "+ evt.isDiscarded());
if (! evt.isDiscarded()) {
ServiceRegistrar[] registrars = null;
try {
registrars = evt.getRegistrars();
} catch(LookupUnmarshalException e) {
e.printStackTrace();
return;
}
for (int i = 0; i < registrars.length; i++) {
System.out.println(registrars[i]);
}
}
} catch (Exception e) { e.printStackTrace(); }
}
else if(event instanceof ExpirationWarningEvent) {
ExpirationWarningEvent evt = (ExpirationWarningEvent)event;
Lease lease = evt.getRenewalSetLease();
try {
lease.renew(1000*60*2);
} catch (Exception e) { e.printStackTrace(); }
System.out.println("Lease renewed for " +
(lease.getExpiration() System.currentTimeMillis()));
}
}
}
ServiceFinder is the helper class for finding services, which was defined in the last chapter.
You pass a ServiceRegistrar (lookup service) to each method. A null can be returned
if the LUS does not contain the desired service. This version does not try to constrain the lookup
with groups or entries. Note the null ServiceTemplate parameters.
package org.jworkplace.util;
import java.rmi.*;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceRegistrar;
167
import net.jini.discovery.LookupDiscoveryService;
import net.jini.event.EventMailbox;
import net.jini.lease.LeaseRenewalService;
public class ServiceFinder
{
public static LookupDiscoveryService findLDS(ServiceRegistrar
registrar) {
LookupDiscoveryService lookupService = null;
ServiceTemplate template;
Class[] lookupDiscoveryServiceInterface =
LookupDiscoveryService.class } ;
template = new ServiceTemplate(null,
lookupDiscoveryServiceInterface, null);
try {
lookupService =
(LookupDiscoveryService)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return lookupService;
}
{
public static LeaseRenewalService findLRS(ServiceRegistrar
registrar) {
LeaseRenewalService leaseService = null;
ServiceTemplate template;
Class[] leaseRenewalServiceInterface =
{ LeaseRenewalService.class } ;
template = new ServiceTemplate(null,
leaseRenewalServiceInterface, null);
try {
leaseService =
(LeaseRenewalService)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return leaseService;
}
public static EventMailbox findEMS(ServiceRegistrar registrar) {
EventMailbox mailbox = null;
ServiceTemplate template;
Class[] eventMailboxInterface = { EventMailbox.class } ;
template = new ServiceTemplate(null, eventMailboxInterface,
null);
try {
mailbox = (EventMailbox)registrar.lookup(template);
} catch (Exception e) { e.printStackTrace(); }
return mailbox;
}
}
Building Activatable Services and Service Monitors
Now that you have classes defined to create activatable services and perform the necessary lease
management, let's provide an example to demonstrate their usage.
To begin, make sure that you have the necessary services running to support the test environment.
You will need the following as minimum requirements:
168
•
•
•
•
•
HTTP server
Lookup service (reggie)
Lookup discovery service (fiddler)
Lease renewal service (norm)
Rmid on all machines that support activation
Figure 7.4 depicts one possible test configuration.
Figure 7.4. One possible test configuration is illustrated in this diagram. Of course,
Jini provides you with great flexibility in deploying and activating services.
If you have not yet started the necessary services, you should start the following:
•
•
•
•
•
Rmid wherever services will be activated
HTTP server where the LUS is running
LUS, such as reggie
LDS, such as fiddler (see Listing 7.5 or use the Start Service GUI)
LRS, such as norm (see Listing 7.8 or use the Start Service GUI)
Compile the Files
To build the example, change to the Chapter 7 example 1 directory and invoke the build command.
C:\JiniJavaSpaces\chapter7\example1\build
Listing 7.15 The build.cmd Script
@echo off
echo ------------------------------------------echo Jini and JavaSpaces Application Development
echo Chapter 7 - Example 1
echo compiling
echo ------------------------------------------if not "%JINI_HOME%" == "" goto staticClasspath
echo You must set JINI_HOME to point to your Jini installation
169
echo For example set JINI_HOME=c:\files\jini1_2
goto exit
echo compiling files
:staticClasspath
echo Setting your CLASSPATH statically.
set CP=
if exist "%JINI_HOME%\lib\jini-core.jar" set
CP=%CP%;%JINI_HOME%\lib\jini-core.jar
if exist "%JINI_HOME%\lib\jini-ext.jar" set
CP=%CP%;%JINI_HOME%\lib\jini-ext.jar
if exist "%JINI_HOME%\lib\sun-util.jar" set
CP=%CP%;%JINI_HOME%\lib\sun-util.jar
echo Using CLASSPATH: %CP%
javac -classpath %CP%;.; *.java -d .
rmic -v1.2 -classpath %CP%
org.jworkplace.service.ServiceListenerProxy -d .
Create Client-Side and Server-Side Jar Files
Now invoke the deploy command to create the jar files. Note that the deploy script adds the
RemoteEvent, RemoteEventListener, and UnknownEventException classes
to the downloaded jar file. This is to ensure that the necessary class files are available to the rmid
activation daemon.
C:\JiniJavaSpaces\chapter7\example1\deploy
Listing 7.16 The deploy.cmd Script
@echo off
@call ..\..\services\scripts\setup.cmd
echo Jini and JavaSpaces Application Development
echo Chapter 7 Example 1
echo ------------------------------------------echo Jini install directory
%JINI_HOME%
echo Examples install directory
%JWORK_HOME%
echo Web server
%HTTP_ADDRESS%
echo ------------------------------------------echo Creating jar file chap7ex1.jar in directory
%JWORK_HOME%\services\lib
echo ...
jar cvf service\chap7ex1.jar org/jworkplace/service/\*.class
org/jworkplace/ util\*.class
copy service\chap7ex1.jar %JWORK_HOME%\services\lib\chap7ex1.jar
echo Creating jar file chap7ex1-dl.jar in directory
%JWORK_HOME%\services\lib
echo ...
jar cvf service\chap7ex1-dl.jar -C org/jworkplace/service/ /
ServiceListenerProxy_Stub.class
jar xvf %JINI_HOME%\lib\jini-core.jar
net/jini/core/event/RemoteEventListener
170
jar uf service\chap7ex1-dl.jar
net/jini/core/event/RemoteEventListener.class
jar xvf %JINI_HOME%\lib\jini-core.jar
net/jini/core/event/UnknownEventException
jar uf service\chap7ex1-dl.jar
net/jini/core/event/UnknownEventException.class
jar xvf %JINI_HOME%\lib\jini-core.jar net/jini/core/event/RemoteEvent
jar uf service\chap7ex1-dl.jar net/jini/core/event/RemoteEvent.class
copy service\chap7ex1-dl.jar %JWORK_HOME%\services\lib\chap7ex1dl.jar
Start the ServiceListener Server
Change to the service directory and invoke the startServiceListener.cmd. The
ServiceListener registers your ServiceListenerProxy with rmid.
Notice that you pass the codebase and the security policy as arguments to ServiceListener.
When the expiration warning notification is sent from the LRS, your
ServiceListenerProxy is activated and renews the lease for an additional two minutes
(see Figure 7.5). You will see the expiration warning event from the LRS sent every minute. In
addition, if you start or stop a LUS in your Jini community, you should see discovery event
notifications sent from the LDS and received by the ServiceListenerProxy.
Figure 7.5. The ServiceListener registers the ServiceListenerProxy with
the activation daemon and then exits. Every minute, the LRS generates a lease
expiration warning event. You should see the ServiceListenerProxy wake-up
and renew the lease for an additional two minutes. This continues until you shut
down rmid and delete the log file.
171
You did not register your ServiceListenerProxy with any LUS. Instead, you made it an
RMI activatable service only. If you want the capability to find the
ServiceListenerProxy using an LUS, you can add the JoinManager and
DiscoveryManagement to the startup code, and register the proxy with any LUSes
discovered.
Summary
An activatable service must initiate when it receives a RemoteDiscoveryEvent. At that
point, it must join the located lookup services, or discard lookup services no longer available. In
addition, it must initiate when it receives an ExpirationWarningEvent, at which time it
must renew its lease for the LeaseRenewalSet that generated the event.
Helper services can be employed to lessen the development effort required, and assist clients and
services with maintaining desirable behavior in a Jini community. This is especially important
with activatable services. The defined framework can be used as a starting point for building the
necessary controls to ensure good Jini citizenship.
172
Chapter 8. Service Administration
IN THIS CHAPTER
•
•
•
•
•
•
Service Administration Overview
Client Administration
The Administrative Interfaces
Building on Your Service Framework
Client-Side Implementation
Summary
This chapter explains how Jini clients administer Jini services. It explains the management of
groups, locators, and entries in service administration. In addition, you will define a Jini service to
provide administrative functionality.
Service Administration Overview
One of the primary goals of Jini is to reduce the amount of administration that is required to
establish and maintain a Jini community.
Services have a broad definition, so trying to define the administrative requirements and a
standard implementation for all services is impossible. However, Jini has defined a standard set of
interfaces that services can implement and extend to satisfy specific requirements. These
interfaces can be used to provide a standard for accessing and controlling service behavior.
Service behavior is controlled by managing the following:
•
•
•
The capture and maintenance of all LUS registrations
The capture and propagation of the unique service ID across all LUS registrations
The consistent capture and maintenance of service attributes and groups across all LUS
registrations
When a service deactivates, this information needs to be saved to persistent storage. Likewise,
when the service activates, it must be retrieved to initialize the service. Other information specific
to a service implementation might also need to be saved.
The Jini service administration interfaces provide the necessary methods to manage these
properties of a Jini service. Additionally, this information provides control to processes that have
traditionally been administratively intensive, such as configuring, deploying, and monitoring
services on a network. The process of discovery and joining is about building service relationships.
It is about finding and binding to services that meet specific criteria. These processes, in addition
to lease management, provide the substrate that allows services to be self-deterministic and to
recover from failure without human intervention.
It is somewhat ironic, but understanding administration in an environment that is relatively
administration-free is a key to understanding Jini.
Client Administration
173
Client administration enables a using entity to configure and control the properties of a serviceproviding entity. This will typically be in the form of a client that is given the appropriate
authorization to perform administrative functions.
All services need some form of administration, even if it is only the initial launch in a Jini network.
But, practically speaking, at a minimum services need a mechanism to control the parameters for
the following:
•
•
•
•
Group membership
Service attributes
Discovery policy
Join policy
The net.jini.admin.Administrable interface is intended to provide a standard
method for services to deliver an administrative object to using entities, so you know whether or
not the service supports administration.
Listing 8.1 The Administrable Interface
public interface Administrable {
public Object getAdmin() throws RemoteException;
}
A client can determine whether a service supports administration by checking whether the service
supports the Administrable interface. It can then access the administrative object by
invoking the getAdmin method.
if(someService instanceof Administrable) {
Object admin = ((Administrable)someService).getAdmin();
The using entity can then determine which administrative interfaces the service supports.
Managing Groups, Service Attributes, Locators
You control service participation in a Jini community by managing the set of groups, attributes,
and locators that a service supports (see Figure 8.1).
Figure 8.1. Jini provides three sets of elements to control visibility and
participation in a Jini community.
174
Group Membership
Jini lookup services are organized into groups. You can create groups with any name that you
desire. There is also a default group called the "public" group. When you start a LUS, you specify
the groups that the LUS will support. In addition, when you discover a LUS, you specify the
groups you require it to support in order to register with the service. In the examples, you have
been setting the %GROUPS% environment variable to public. Figure 8.2 demonstrates the results
when services are registered along departmental boundaries.
Figure 8.2. When the Human Resources service attempts to discover registration
points (LUS), it will only hear back from lookup services that support the groups
requested.
175
Group objects are merely represented as a set of java.lang.String[] objects. Services
can change their group membership at any time, and using entities can optionally receive
notification that membership has changed.
Service administration provides the necessary methods to support setting, modifying, and adding
groups to any service definition that supports join or discovery administration.
Service Attributes
Recall that service attributes are defined as a set of net.jini.core.entry.Entry[]
entries. Entry objects are used to augment the service definition so using entities can perform
specific searches for matching services.
Jini supplies a number of predefined attributes; for instance, Address and Location entries,
and Name and ServiceInfo entries. It is also possible to define attributes that are unique to a
particular service. Attributes can be added and removed at any time during the lifecycle of a
service.
Attributes that do not permit clients to administer their fields should implement the
ServiceControlled interface.
public interface net.jini.lookup.entry.ServiceControlled { }
This is simply a marker interface that can be used to indicate to administrative services that this
entry (attribute) is not client-modifiable.
Lookup Locators
Lookup locators provide the third critical data element to Jini services. Locators determine where
on the network to discover lookup services and services in general. As discussed in Chapter 4,
"Building on Jini Foundation Concepts," unicast discovery uses lookup locators to define specific
addresses (hostnames or IP addresses) to search for services. Lookup locators are defined as a set
of net.jini.core.discovery.LookupLocator[] objects.
Service administration provides methods to manage the set of lookup locators that a service will
use, and this directly affects where on the network the service will reside.
Up to this point in the book, services have been defined using static configurations. For instance,
we did not alter group membership, service attributes, or service location information. Despite this,
Jini demonstrated its flexibility through the discovery and join process.
Now let's look at the interfaces that support the administration and control of services in the Jini
community.
The Administrative Interfaces
There are three administrative interfaces that comprise the set of interfaces used to control and
manage Jini services. They are as follows:
•
The net.jini.admin.JoinAdmin interface is used to control a service's
participation in the join protocol.
176
•
•
The com.sun.jini.admin.StorageLocationAdmin interface permits a
client to specify where a service keeps its persistent data.
The com.sun.jini.admin.DestroyAdmin interface permits a client to
destroy a service (that is, delete persistent storage, unregister the service, and exit the
VM).
A common convention is to extend these interfaces and add service-specific interfaces:
public interface MyServiceAdmin extends JoinAdmin,
StorageLocationAdmin, DestroyAdmin
{
public myServiceAdminMethod() throws RemoteException;
// other methods
}
You then implement the methods that comprise the set of administrative services supported.
In addition, there are two LUS-specific interfaces.
•
•
The net.jini.lookup.DiscoveryAdmin interface is used to control which
groups a LUS is a member of; and for controlling which TCP port a LUS uses for its
lookup locator.
The com.sun.jini.reggie.RegistrarAdmin interface is an administrative
interface for the Sun Microsystems-supplied reggie implementation of the LUS.
Now let's look at the services in detail.
The JoinAdmin Interface
After you have determined that a service supports administration—it implements the
Administrable interface—you can retrieve the administration object. Using a similar
technique, you can determine the interfaces supported by the administrative object.
JoinAdmin joinAdmin = null;
try {
if(someService instanceof Administrable) {
Object admin = ((Administrable)someService).getAdmin();
if(admin instanceof JoinAdmin)
joinAdmin = (JoinAdmin) admin;
}
} catch(Exception e) { }
// invoke joinAdmin methods
Most services will implement the JoinAdmin interface because it provides methods to control
the three fundamental elements of Jini service composition and behavior—groups, attributes, and
locators.
Listing 8.2 The JoinAdmin Interface
public interface JoinAdmin {
public void addLookupAttributes(Entry[] attrSets);
public void addLookupGroups(java.lang.String[] groups);
public void addLookupLocators(LookupLocator[] locators);
public Entry[] getLookupAttributes();
public java.lang.String[] getLookupGroups();
177
public LookupLocator[] getLookupLocators();
public void modifyLookupAttributes(Entry[] attrSetTemplates,
Entry[] attrSets);
public void removeLookupGroups(java.lang.String[] groups);
public void removeLookupLocators(LookupLocator[] locators);
public void setLookupGroups(java.lang.String[] groups);
public void setLookupLocators(LookupLocator[] locators);
}
The JoinAdmin interface enables using entities to control how the service joins the Jini
community.
The StorageLocationAdmin Interface
The StorageLocationAdmin interface enables a client to specify where a service keeps its
persistent data. For instance, good Jini behavior mandates that services use a consistent service ID
across registrations and that they "remember" the configuration (groups, attributes, and locators)
used when the service is restarted. The StorageLocationAdmin interface specifies where
that information resides.
Listing 8.3 The StorageLocationAdmin Interface
public interface StorageLocationAdmin
{
public abstract String getStorageLocation();
public abstract void setStorageLocation(String location);
}
The setStorageLocation method should specify the name of a directory or storage access
point that can be identified by a String. This method will create a log file that captures
configurations and service-specific information. The process does the following:
1. Grabs a write lock to ensure exclusive access to the file.
2. Creates the log file, or copies the current log file to a new location.
3. Takes a new snapshot of the current service state.
4. If it's an activatable service, it passes the file location to the ActivationSystem.
5. Releases the write lock.
Some thought must go into implementing this method if an existing service is running while the
log file is being moved. Separating service administration information from service snapshots, for
instance ongoing incremental updates, can help to minimize the impact and avoid excessive
queuing and loss of data.
The DestroyAdmin Interface
Using entities that want to destroy a service (delete persistent storage, unregister the service, and
then exit the VM) should use the DestroyAdmin interface.
Listing 8.4 The DestroyAdmin Interface
178
public interface DestroyAdmin {
public void destroy();
}
The DiscoveryAdmin Interface
This interface is specific to the lookup service. It enables clients to administer the discovery
parameters—primarily group membership of a LUS.
Listing 8.5 The DiscoveryAdmin Interface
public interface DiscoveryAdmin
{
void addMemberGroups(String[] groups);
String[] getMemberGroups();
int getUnicastPort();
void removeMemberGroups(String[] groups);
void setMemberGroups(String[] groups);
void setUnicastPort(int port);
}
Often a LUS will be defined to support a single group or department. For instance, the sales group
would be supported by one or more dedicated lookup services. The groups are administered
through this interface.
The RegistrarAdmin interface is unique to the reggie implementation of the
ServiceRegistrar. It uses interface composition to provide standard service
administration and extends that definition with reggie-specific methods. These methods include
setting lease duration values and managing the heuristics associated with logging.
Listing 8.6 The RegistrarAdmin Interface
public interface RegistrarAdmin
extends DiscoveryAdmin, StorageLocationAdmin, DestroyAdmin,
JoinAdmin
{
/**
* Change the lower bound for the maximum value allowed by the
* lookup service for any service lease, in milliseconds.
*/
void setMinMaxServiceLease(long leaseDuration) throws
RemoteException;
/**
* Retrieve the lower bound for the maximum value allowed by the
* lookup service for any service lease, in milliseconds.
*/
long getMinMaxServiceLease() throws RemoteException;
/**
* Change the lower bound for the maximum value allowed by the
* lookup service for any event lease, in milliseconds.
*/
void setMinMaxEventLease(long leaseDuration) throws
RemoteException;
/**
* Retrieve the lower bound for the maximum value allowed by the
179
* lookup service for any event lease, in milliseconds.
*/
long getMinMaxEventLease() throws RemoteException;
/**
* Change the minimum average interval between lease renewals, in
* milliseconds.
*/
void setMinRenewalInterval(long interval) throws RemoteException;
/**
* Retrieve the minimum average interval between lease renewals,
in
* milliseconds.
*/
long getMinRenewalInterval() throws RemoteException;
/**
* Change the weight factor applied by the lookup service to the
* snapshot size
*/
void setSnapshotWeight(float weight) throws RemoteException;
/**
* Retrieve the weight factor applied by the lookup service to
the
* snapshot size
*/
float getSnapshotWeight() throws RemoteException;
/**
* Change the value of the size threshold of the snapshot;
*/
void setLogToSnapshotThreshold(int threshold) throws
RemoteException;
/**
* Retrieve the value of the size threshold of the snapshot
*/
int getLogToSnapshotThreshold() throws RemoteException;
}
Building on Your Service Framework
Now let's build on the service framework to include administrative capabilities.
The ServiceAdmin Interface
A typical service interface will combine the JoinAdmin, DestroyAdmin, and
StorageLocationAdmin interfaces. As stated, these interfaces define the methods
required to manage Jini services. The ServiceAdmin interface is an example.
Listing 8.7 The ServiceAdmin Interface
package org.jworkplace.service;
import net.jini.admin.JoinAdmin;
180
import com.sun.jini.admin.DestroyAdmin;
import com.sun.jini.admin.StorageLocationAdmin;
public interface ServiceAdmin extends JoinAdmin, DestroyAdmin,
StorageLocationAdmin { }
Here the common set of administrative services is extended by using an interface composition
technique.
Next you define a backend interface that services will implement by adding the Remote
interface. The ServiceBackEnd interface extends Administrable, ServiceAdmin,
and Remote. This will be the interface that all your remote services will implement.
Listing 8.8 The ServiceBackEnd Interface
package org.jworkplace.service;
import java.rmi.Remote;
import net.jini.admin.Administrable;
import org.jworkplace.service.ServiceAdmin;
public interface ServiceBackEnd extends Administrable, ServiceAdmin,
Remote { }
The ServiceAdmin Implementation
Now let's define and implement the methods necessary to manage a service in the Jini community.
This will serve as an abstract base class that you can extend to provide specific-service
functionality. All the services defined to this point require participation in lookup and discovery.
This is fundamental to becoming a citizen in the Jini community. You can capture the primary
elements that assist and control this process in your base class. They include the following:
•
•
•
•
•
•
•
Join Manager—Manages the join process
Lookup Discovery Manager—Manages the discovery process
Lookup Locators—Controls the unicast discovery process
Groups—Organize services
Attributes—Augment service definition
Service Proxy—Implements the using entity (client)
Service ID—Uniquely identifies the service
ServiceImpl Abstract Class
The ServiceImpl class contains the necessary mechanisms to control and administer a Jini
service, as shown in Figure 8.3.
Figure 8.3. The abstract ServiceImpl class provides a base class for services to
extend.
181
Listing 8.9 The ServiceImpl Class
public abstract class ServiceImpl implements ServiceBackEnd {
private
private
private
private
private
private
private
JoinManager joiner = null;
LookupDiscoveryManager lookupDiscMgr;
LookupLocator locators[];
String groups[] = DiscoveryGroupManagement.ALL_GROUPS;
Entry[] attributes;
Object proxy;
ServiceID serviceID = null;
There are two abstract methods defined in ServiceImpl:
public abstract Entry[] getAttributes();
public abstract Object getProxy() throws IOException;
The getAttributes method must be implemented by concrete classes to provide the service
attribute information used to register with an LUS.
The getProxy method must be implemented to provide the using entity (client)
implementation. This is the object that is downloaded to the client if it is a remote interface.
ServiceImpl provides a null reference to the getAdmin method of the
Administrable interface. Classes that extend ServiceImpl should override this method
and provide an administrative object of the appropriate type.
/* Administrable Interface */
public Object getAdmin() throws RemoteException {
return null;
}
ServiceImpl performs the following steps:
1. Exports the service (itself) to RMI.
2. Creates a snapshot thread to take snapshots of the service as it is running.
3. Creates a reliable log handler that is used to store the log and snapshot information.
4. Recovers any existing service log information.
182
5. Initializes the service by discovering and joining the Jini community with the state
retrieved from the service logs.
6. Starts the snapshot thread.
/** our persistent store used to save state */
private PersistentStore store;
/** The name of the log directory where state will reside*/
private String logDir;
/** Thread that performs snapshots when signaled */
private SnapshotThread snapshotter;
public ServiceImpl(String[] args) throws IOException {
// export to RMI
UnicastRemoteObject.exportObject(this);
// Get Log Directory Name from the command line
logDir = args[0];
System.out.println("Using log directory " + logDir);
// start a thread to perform service specific updates
snapshotter = new SnapshotThread();
try {
// create a logging subsystem
store = new PersistentStore(logDir, new ServiceLogHandler(),
this);
} catch (IOException e) { e.printStackTrace(); }
// get required attributes and proxy and start join process
init();
// current baseline
store.snapshot();
// start thread
snapshotter.start();
}
The init method is used to create the LookupDiscoveryManager and register the
service using a JoinManager. This is where the calls to the getProxy and
getAttributes are made into subclasses.
protected void init() throws IOException {
try {
lookupDiscMgr = new LookupDiscoveryManager(groups, locators,
null);
} catch (IOException e) {
System.err.println("JoinState:Problem starting discovery");
e.printStackTrace();
throw new IOException("JoinState: Problem starting discovery:"
+ e.getLocalizedMessage());
}
// subclasses will supply the proxy object
proxy = getProxy();
183
// subclasses will supply the service attributes
attributes = getAttributes();
/* Register this service with any configured lookup services */
if (serviceID == null) {
// First instance ... need service
id
joiner = new JoinManager(
proxy,
// service object
attributes,
// service attributes
new SrvcIDListener(),
// ServiceIDListener
lookupDiscMgr,
// DiscoveryManagement - default
null);
// LeaseRenewalManager - default
} else {
// Rejoin with (recovered) state information
joiner = new JoinManager(
proxy,
// service object
attributes,
// service attributes
serviceID,
// Service ID
lookupDiscMgr,
// DiscoveryManagement - default
null);
// LeaseRenewalManager - default
}
}
Logging
You will need to provide a framework for logging service information. The ServiceImpl
class uses some of the same techniques that are implemented in Jini services, such as mercury and
norm.
A service must be capable of capturing configurations and changes to service definition. These
include:
•
•
•
•
The set of lookup attributes
The set of lookup groups
The set of lookup locators
The service ID
In addition, a service might need to store service-specific information such as session data or
transaction state.
If you have already run the service and received a service ID from the LUS, then you must use
that ID on subsequent restarts. The first time the service runs, you supply a
net.jini.lookup.ServiceIDListener to JoinManager. The
ServiceIDListener is notified by the manager when the service ID is received from the
LUS. The serviceIDNotify method is invoked with the service ID. You save the ID by
writing a log record to persistent storage.
Listing 8.10 The SrvcIDListener Class
/**
* Class which handles the callback of a service ID assignment
* by the JoinManager
*/
private class SrvcIDListener implements ServiceIDListener
{
public SrvcIDListener() {
184
super();
}
/**
* The JoinManager will invoke this method when it receives a
* valid ServiceID from a lookup service.
*/
public void serviceIDNotify(ServiceID id) {
// Set the ID
setServiceID(id);
System.out.println("Received service id " + id);
// Log this event
try {
store.snapshot();
} catch(IOException e) { e.printStackTrace(); }
}
}
/**
* Utility method for setting the service's ID obtained from
* the lookup registration process.
*/
private void setServiceID(ServiceID id) {
serviceID = id;
}
Your persistent storage is created in the ServiceImpl constructor.
try {
store = new PersistentStore(logDir, new ServiceLogHandler(), this);
} catch (IOException e) { e.printStackTrace(); }
This PersistentStore class is a slight modification to the PersistentStore class
used in the lookup discovery service supplied with the Jini distribution.
You create a PersistentStore by passing the following:
•
•
•
The directory where the store should persist its data
An object that implements com.sun.jini.releableLog.LogHandler class
that will process the log and recover the service's state
A ServiceImpl object that is called back after an update so it can decide whether or
not to take a snapshot.
Listing 8.11 The PersistentStore Class
class PersistentStore {
// The ReliableLog reference
private ReliableLog log;
// Support for multiple readers – one writer semantics
final private ReadersWriter mutatorLock = new ReadersWriter();
/** Location of the persistent store */
final private File storeLocation;
/** Object that handles the recovery of logs */
final private LogHandler logHandler;
185
/** The Service we are part of */
final private ServiceImpl server;
/** Number of updates since last snapshot */
private int updateCount;
PersistentStore(String logDir, LogHandler logHandler, ServiceImpl
server)
throws IOException
{
this.logHandler = logHandler;
this.server = server;
storeLocation = new File(logDir);
log = new ReliableLog(storeLocation.getCanonicalPath(),
logHandler);
// this will be passed to the ServiceImpl log handler
log.recover();
}
The com.sun.jini.reliableLog package contains a set of classes that can assist you in
writing a reliable log process. You simply have to extend the abstract class LogHandler.
When you create the PersistentStore, a
com.sun.jini.reliableLog.ReliableLog is created. The ReliableLog
constructor requires a user-defined class that extends
com.sun.jini.reliableLog.LogHandler.
Listing 8.12 The LogHandler Class
public abstract class LogHandler {
/**
* Creates a LogHandler for a ReliableLog.
*/
public LogHandler() { }
/**
* This callback is invoked when the client calls the snapshot
method
* of ReliableLog.
*/
public abstract void snapshot(OutputStream out) throws Exception;
/**
* This callback is invoked when the client calls the recover
method of
* ReliableLog.
*/
public abstract void recover(InputStream in) throws Exception;
/**
* This callback is invoked when the client calls the update method
of
* ReliableLog.
*/
public void writeUpdate(OutputStream out, Object value) throws
Exception
186
{
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject(value);
s.flush();
}
/**
* This callback is invoked during recovery, once for every record
in the
* log. After reading the update, this method invokes the
applyUpdate
* (abstract) method in order to execute the update.
*/
public void readUpdate(InputStream in) throws Exception {
ObjectInputStream s = new ObjectInputStream(in);
applyUpdate(s.readObject());
}
/**
* This callback is invoked during recovery, once for every record
in the
* log. After reading the update, this method is invoked in order
to
* execute the update.
*/
public abstract void applyUpdate(Object update) throws Exception;
}
To create an implementation of a LogHandler, you must implement the abstract methods
applyUpdate, snapshot, and recover. The ServiceLogHandler is an
implementation of the Jini LogHandler abstract class. It is an inner class, defined in the
ServiceImpl abstract class.
Listing 8.13 The ServiceLogHandler Class
// the ServiceImpl (inner class) reliable log file handler
public class ServiceLogHandler extends
com.sun.jini.reliableLog.LogHandler
{
public void applyUpdate(Object update) throws Exception { }
public void snapshot(OutputStream out) throws IOException {
ObjectOutputStream oostream = new ObjectOutputStream(out);
oostream.writeObject(serviceID);
// get the attributes from the JoinManager
writeAttributes(joiner.getAttributes(), oostream);
// the groups and lookups from the lookup discovery manager
oostream.writeObject(lookupDiscMgr.getGroups());
oostream.writeObject(lookupDiscMgr.getLocators());
oostream.flush();
}
public void recover(InputStream in) throws Exception {
ObjectInputStream oistream = new ObjectInputStream(in);
serviceID = (ServiceID)oistream.readObject();
attributes = readAttributes(oistream);
groups
= (String[])oistream.readObject();
187
locators
= (LookupLocator[])oistream.readObject();
}
}
When your Jini service starts, the PersistentStore calls recover on the
ReliableLog, which passes the request to the recover method of the user-defined
ServiceLogHandler. The recover method of the ServiceLogHandler (see
Listing 8.12) provides a default implementation for restoring the service ID and the groups and
locators used to configure the JoinManager. This provides you with the persistent state
required to restore the service and implement good Jini citizenship behavior.
A snapshot provides a moment-in-time capture of the current state of a service. A snapshot is
taken of the current state of the service after the service is started. The snapshot method of the
ServiceLogHandler (see Listing 8.12) retrieves the current state of the service from the
Join Manager and the LookupDiscoveryManager. It then saves the service
information to safe storage. The synchronization of processing a single writer is achieved in the
Jini service through a "reader/writer" mutex construct. This process enables only one writer at a
time, but permits an unlimited number of simultaneous readers as long as no writer has locked the
resource. The following code fragment from the PersistentStore class ensures that only
one writer can update the service log. It grabs a write lock, and then calls snapshot on the
ServiceLog Handler. It releases the write lock once the log update has completed.
void snapshot() throws IOException {
try {
// Using write lock because we want an exclusive lock
mutatorLock.writeLock();
updateCount = 0;
// Don't need to sync on this because
// mutatorLock.writeLock() gives us an exclusive lock
log.snapshot();
} finally {
// Using write lock because we want an exclusive lock
mutatorLock.writeUnlock();
}
}
The PersistentStore update method ensures that the locking process is synchronized
and logs the update Object. It then calls the ServiceImpl updatePerformed method
to allow the service to determine whether a snapshot should be taken.
void update(Object o) {
final Long lockStateVal = (Long)lockState.get();
if (lockStateVal == null || lockStateVal.longValue() == 0)
throw new IllegalStateException("PersistentStore.update:" +
"Must acquire mutator lock before calling update()");
synchronized (this) {
try {
log.update(o, true);
updateCount++;
server.updatePerformed(updateCount);
} catch (IOException e) {
// $$$ should probably be propagating this
exception
System.err.println("IOException while updating log");
188
e.printStackTrace();
}
}
}
The updatePerformed method of ServiceImpl can be overridden to perform servicespecific processing, such as determining what information should be captured and how often.
Listing 8.14 The SnapshotThread Class
// ServiceImpl called by PersistentStore
public void updatePerformed(int updateCount) {
snapshotter.takeSnapshot();
}
/**
* Thread that performs the actual snapshots, done in a separate
thread
* so it will not hang up in progress remote calls
*/
private class SnapshotThread extends Thread {
/** Create a daemon thread */
private SnapshotThread() {
super("snapshot thread");
setDaemon(true);
}
/** Signal this thread that it should take a snapshot */
private synchronized void takeSnapshot() {
notifyAll();
}
public void run() {
while (!isInterrupted()) {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
return;
}
}
try {
store.snapshot();
} catch (InterruptedIOException e) {
return;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
The SnapShotThread is started in the init method of ServiceImpl. It calls the
PersistentStore snapshot method, which performs the concurrency checks mentioned
and ensures a single log writer.
Service Administration Implementation
189
You now have the necessary framework to do the following:
•
•
•
•
•
Export a service to RMI
Start the logging and recovery process
Register the service with the LUS
Export the client proxy
Capture the fundamental administration elements to persistent storage
Now let's look at how to wire this framework into the administration interfaces. It is surprisingly
simple and straightforward.
JoinAdmin Implementation
Most services will implement the JoinAdmin interface because it provides methods to control
the three fundamental elements of Jini service composition and behavior: attributes, groups, and
locators. By using the JoinManager (joiner) and the LookUpDiscovery manager
(lookupDiscMgr) that are defined in the ServiceImpl class, join administration can
simply be delegated to these managers as shown in Listing 8.15.
Attribute sets are changed, added, and retrieved by using the JoinManager. When you change
service attributes, the JoinManager will update the associated attributes in all registered
LUSes.
Groups and locators are managed by using the LookupDiscovery manager. Managing the
set of lookup locators allows you to control the addresses the service uses for unicast discovery.
Managing the set of groups allows you to control the LUSes the service will join. When you
change the locators or groups the service supports, the discovery and join process will update all
relevant LUS registrations.
Listing 8.15 The ServiceImpl Class Continued
/* Implementation of the JoinManager Interface */
** This method allows you to augment the current service definition with
*/ additional entries using the join manager
public void addLookupAttributes(Entry[] attrSets) throws RemoteException
{
joiner.addAttributes(attrSets);
}
/* This method allows you to add additional supported groups using the
*/ lookup discovery manager
public void addLookupGroups(java.lang.String[] groups) throws RemoteException
{
try {
lookupDiscMgr.addGroups(groups);
} catch (IOException e) { throw new RemoteException(e.toString()); }
}
/* This method allows you to add additional locators using the
*/ lookup discovery manager
public void addLookupLocators(LookupLocator[] locators) throws RemoteException
{
lookupDiscMgr.addLocators(locators);
}
// The following methods provide retrieval of attributes, groups, and locators
190
public Entry[] getLookupAttributes() throws RemoteException
{
return joiner.getAttributes();
}
public java.lang.String[] getLookupGroups() throws RemoteException
{
return lookupDiscMgr.getGroups();
}
public LookupLocator[] getLookupLocators() throws RemoteException
{
return lookupDiscMgr.getLocators();
}
// Modification of attributes will be reflected in LUS registrations
public void modifyLookupAttributes(Entry[] attrSetTemplates, Entry[] attrSets)
throws RemoteException
{
joiner.modifyAttributes(attrSetTemplates, attrSets);
}
// Removal of groups will trigger notifications to interested parties through the
ServiceRegistrar
public void removeLookupGroups(java.lang.String[] groups)
throws RemoteException
{
lookupDiscMgr.removeGroups(groups);
}
// Removal of locators eliminates specific LUS hosts from being discovered and
thereby joined
public void removeLookupLocators(LookupLocator[] locators)
throws RemoteException
{
lookupDiscMgr.removeLocators(locators);
}
// Will impact LUS registrations
public void setLookupGroups(java.lang.String[] groups)
throws RemoteException
{
try {
lookupDiscMgr.setGroups(groups);
} catch (IOException e) { throw new RemoteException(e.toString()); }
}
// Will impact LUS registrations
public void setLookupLocators(LookupLocator[] locators)
throws RemoteException
{
lookupDiscMgr.setLocators(locators);
}
StorageLocationAdmin Implementation
This implementation of the StorageLocationAdmin interface enables administrative
clients to specify a persistent storage location. The service will use this location to save, update,
and recover logged information. The init method defined in ServiceImpl is setting this
location (logDir) from a command line argument.
191
/*
** Discovery Storage Location implementation
*/
// return the current logging directory
public String getStorageLocation() throws RemoteException {
return new String(logDir);
}
// warning does not try to synchronize existing logs or process
public void setStorageLocation(String location) throws
IOException,
RemoteException {
logDir = location;
}
}
DestroyAdmin Implementation
This implementation of the DestroyAdmin interface enables administrative clients to remove
the persistent data associated with the service and unregister the service with any activation
system and LUS. The ServiceImpl implementation calls the PersistentStore which
invokes the ReliableLog to close the log file, remove all related files from the log directory,
and delete the directory.
/*
** Destroy Admin implementation
*/
public void destroy() throws RemoteException {
try {
store.destroy();
} catch (IOException io) {
throw new RemoteException("Unable to destroy log " +
io.getLocalizedMessage());
}
}
Client-Side Implementation
The client-side implementation simply delegates the calls to the remote server. This class can be
extended to satisfy unique service administration requirements as seen in Figure 8.4.
Figure 8.4. Clients invoke the ServiceAdminProxy, which implements the
ServiceAdmin interface. In this example, the proxy delegates the request to
service implementation.
192
The ServiceAdminProxy is the client-side implementation of the ServiceAdmin
interface. When the client determines the service supports the Administrable interface, it
invokes the getAdmin method on the ServiceImpl implementation. The getAdmin
method is overridden by implementations of the ServiceImpl abstract class. You can create
and return a ServiceAdmin Proxy in your getAdmin implementation to provide the
functionality discussed in this chapter. The proxy simply delegates the calls to the remote server.
Listing 8.16 The ServiceAdminProxy Class
package org.jworkplace.admin;
import
import
import
import
import
import
import
import
import
import
java.rmi.Remote;
java.rmi.RemoteException;
java.io.Serializable;
java.io.IOException;
net.jini.admin.JoinAdmin;
net.jini.lookup.DiscoveryAdmin;
net.jini.core.entry.Entry;
net.jini.core.discovery.LookupLocator;
com.sun.jini.admin.DestroyAdmin;
com.sun.jini.admin.StorageLocationAdmin;
import org.jworkplace.service.*;
public class ServiceAdminProxy implements ServiceAdmin, Serializable
{
private final ServiceBackEnd server;
public ServiceAdminProxy(ServiceBackEnd server) throws
RemoteException
{
super();
this.server = server;
}
public void addLookupAttributes(Entry[] attrSets) throws
RemoteException
{
193
server.addLookupAttributes(attrSets);
}
public void addLookupGroups(java.lang.String[] groups) throws
RemoteException
{
server.addLookupGroups(groups);
}
public void addLookupLocators(LookupLocator[] locators) throws
RemoteException
{
server.addLookupLocators(locators);
}
public Entry[] getLookupAttributes() throws RemoteException
{
return server.getLookupAttributes();
}
public java.lang.String[] getLookupGroups() throws RemoteException
{
return server.getLookupGroups();
}
public LookupLocator[] getLookupLocators() throws RemoteException
{
return server.getLookupLocators();
}
public void modifyLookupAttributes(Entry[] attrSetTemplates, Entry[]
attrSets) throws RemoteException
{
server.modifyLookupAttributes(attrSetTemplates, attrSets);
}
public void removeLookupGroups(java.lang.String[] groups)
throws RemoteException
{
server.removeLookupGroups(groups);
}
public void removeLookupLocators(LookupLocator[] locators)
throws RemoteException
{
server.removeLookupLocators(locators);
}
public void setLookupGroups(java.lang.String[] groups)
throws RemoteException
{
server.setLookupGroups(groups);
}
public void setLookupLocators(LookupLocator[] locators)
throws RemoteException
{
server.setLookupLocators(locators);
}
/*
** Discovery Storage Location implementation
*/
194
public String getStorageLocation() throws RemoteException {
return server.getStorageLocation();
}
public void setStorageLocation(String location) throws IOException,
RemoteException {
server.setStorageLoaction(location);
}
/*
** Destroy Admin implementation
*/
public void destroy() throws RemoteException {
server.destroy();
}
Summary
One of the primary goals of Jini is to reduce the amount of administration that is required to
establish and maintain a Jini community.
Jini has defined a standard set of administration interfaces that services can implement and extend.
These interfaces can be used to provide a standard for accessing and controlling service behavior.
Service behavior is reflected in the set of groups, attributes, and locators that a service supports.
The abstract ServiceImpl class provides you with a base class for defining
UnicastRemote Object servers. Using this class, and the associated helper classes, can
reduce the amount of development effort required to produce robust Jini services that exhibit good
Jini citizenship behavior.
195
Chapter 9. Security in Jini
IN THIS CHAPTER
•
•
•
•
•
•
•
•
Defining Security Requirements
Jini-Specific Requirements
Java Security
The Java Virtual Machine
Configuring Basic Security with Policy Files
Additional Security Considerations and Alternative Techniques
In Need of Evolution
Summary
How is security implemented in Jini networks? This chapter explains the current state of security
in Jini technology. It highlights the dependencies on the Java 2 platform, RMI, and the Java
language. In addition, common security mechanisms and standards for distributed Jini services are
evaluated.
Security is a hot topic. With the growth of the Internet, it has become an issue that often headlines
the nightly news. These stories can sound more like biological warfare than computer technology,
as it has become common to talk of the spread of viruses, bugs, and infections.
Despite the heightened awareness and risk, systems are still being deployed that are more tightly
coupled to the global infrastructure—the Internet—than ever before. Security is still a risk versus
reward proposition.
How much security do you need to protect the assets you have?
Every Information Technology department has had to answer this question. Unfortunately for
most, the security deployed is lacking, leaving the company's systems open to users, partners, and
the Internet at large. However, the cures are forthcoming.
Defining Security Requirements
Traditionally, security is defined as the protection of information, systems, and services against
manipulation, mistakes, and disasters. Network security is comprised of authentication,
authorization, integrity, confidentiality, and non-repudiation.
Authentication is the most common type of network security. It generally involves a user or
process demonstrating some form of evidence to prove identity. Such evidence may be
information only the user would likely know (a password), or it may be information only the user
could produce (signed data using a private key).
Authorization involves the ability to enforce access controls upon an authenticated user.
Commonly implemented as an access control policy that provides an association between a user's
access rights and system resources, such as databases, files, and processes.
Integrity ensures that messages are delivered correctly, and that messages in transit have not been
tampered with maliciously.
196
Confidentiality and privacy ensure that data cannot be seen or disclosed over the network by
outside parties. Encryption is a procedure used to convert text into code in order to prevent any but
the intended recipient from reading that data. It can be used to ensure the confidentiality of data,
the authentication of the data sender, or the integrity of the data sent.
Finally, non-repudiation guarantees that a sender cannot deny having sent a particular message, as
well as the reverse—a sender cannot claim to have sent a message that he did not.
These processes are fundamental to network security implementations. However, Jini, because of
its dynamic nature both in terms of code movement and service discovery, continues to stretch
network security in new directions. Security frameworks that address the Jini environment
adequately are just now surfacing.
Jini-Specific Requirements
Jini requires the standard network authentication-type security. In addition, Jini needs to provide
an environment that supports standard security processes for mobile and downloaded code.
Jini should provide an API that supports a variety of network security implementations, as well as
increased application portability across different network security implementations. It should
provide integration with other Java platform security mechanisms, such as the Java Authentication
and Authorization Service (JAAS), in addition to enabling a variety of access control
(authorization) schemes to be used. Additional information on JAAS can be found at
http://java.sun.com/products/jaas/.
An extensible framework should support security needs and trust relationships that can vary
according to the client, server, operation, and data involved. To leverage the power of service
discovery, a balance has to be reached between what's visible and reachable and what's protected
and confidential. The framework should support mechanisms that are adaptable to specific
implementations.
These requirements have been difficult to meet. The diversity of systems, security standards,
deployment alternatives, and the complexity of integration have impeded progress.
A major development area in the next release of Jini—The Davis Project—is an effort to address
the need for a Jini technology security architecture (see www.jini.org/projects). As of this writing,
the Davis Project has produced the Overture release (currently available for download) that
includes the basic security architecture necessary to support many of these requirements.
Java by its very nature has been addressing security from its inception. Before diving directly into
Jini security, let's discuss the Java security framework.
Java Security
Java security has been evolving and changing at a rapid pace. Access control has evolved to be far
more fine grained with each version of the Java platform.
The following list highlights the most significant changes in the JDK releases.
197
•
•
•
JDK 1.0—In the sandbox model, local code is trusted to have full access to system
resources, such as the file system; however, downloaded remote code is not trusted, and
can access only the limited resources provided inside the sandbox.
JDK 1.1—Introduces the concept of digitally signed code for applets. Digitally signed
code is treated like local code, with full access to resources, if the public key used to
verify the signature is trusted. Unsigned code still runs in the sandbox. Signed applets are
delivered, with their respective signatures, in signed jar (Java ARchive) files.
JDK 1.2—All code, regardless of whether it is local or remote, can now be subject to a
security policy. The security policy defines the set of permissions available for code from
various signers or locations and can be configured by a user or a system administrator.
Each permission specifies a permitted access to a particular resource, such as read and
write access to a specified file or directory, or connect access to a given host and port.
The runtime system organizes code into individual domains, each of which encloses a set
of classes whose instances are granted the same set of permissions. A domain can be
configured to be equivalent to the sandbox, so downloaded code can still be run in a
restricted environment if the user or the administrator so chooses. As before, applications
run unrestricted by default, but can optionally be subject to a security policy.
•
JDK 1.3, 1.4—Access control decisions can now be based both on the executing code's
CodeSource, as well as on the user running the code, or the Subject. With JDK
1.3, the Java Authentication and Authorization Service was packaged as an extension. In
1.4, this service has moved into the standard runtime distribution.
The Java Virtual Machine
The Java Virtual Machine (JVM), from a security perspective, provides the sandbox that ensures
code runs in a protected space and has limited access to system resources. The key components in
the JVM security framework are the following:
•
•
•
Class loader
Class file verifier
Security manager
The Class Loader
The class loader is used by the JVM to locate and load into memory the classes necessary to
execute a Java program. As has been demonstrated with Jini and RMI, not all class files that are
needed come from the local file system.
Classes can be divided into two categories—trusted and untrusted.
Figure 9.1. The Java Virtual Machine.
198
Trusted classes are those classes that are assumed to be safe. In Java 2, these were restricted to
only the Java Runtime Environment (JRE) classes. These are the classes that are found in the boot
class path.
Untrusted classes are all classes that fall outside of the boot class path. This includes the files that
are downloaded over the network. These files are verified by the class file verifier.
The Class File Verifier
The class file verifier is invoked by the class loader to ensure that a file meets certain criteria. A
series of checks is performed:
1. File Integrity Check—Ensures that a file has the appropriate signature and length.
2. Class Integrity Check—Ensures that all classes, methods, and fields have legal names and
signatures.
3. Bytecode Integrity Check—The runtime behavior of the bytecodes is examined and
verified.
4. Runtime Integrity Check—Extends the bytecode integrity check to look for code that
actually gets executed. This is to ensure that code that is never executed does not have to
go through the entire verification process unnecessarily.
The Security Manager
The security manager is responsible for the runtime restrictions that are imposed on code after the
code has gone through the verification process. The security manager enforces restrictions based
on security policy statements.
The class java.lang.SecurityManager supports the policy-driven security model. It
enables an application to determine, before performing a possibly unsafe or sensitive operation,
what the operation is and whether it is being attempted in a security context that will enable the
operation to be performed. The application can then allow or disallow the operation.
199
The SecurityManager class contains methods with names that begin with the word check.
The invocation of such a check method typically looks like this:
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkXXX(argument, . . . );
}
The security manager is given an opportunity to prevent completion of the operation by throwing
an exception. A security manager simply returns if the operation is permitted, but throws a
SecurityException if the operation is not permitted.
The current security manager is set by the setSecurityManager method in the class
System. The current security manager is obtained by the getSecurityManager method.
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
RMISecurityManager provides a security manager for use by RMI and Jini applications
that use downloaded code. RMI's class loader will not download any classes from remote
locations unless a security manager has been set. This is a common mistake in Jini development,
because often no warning is given as to why the service has failed.
In your examples, you have been setting the security manager in your main methods:
System.setSecurityManager(new RMISecurityManager());
To determine whether the code has sufficient permissions, the SecurityManager
implementation delegates responsibility to the java.security.AccessController,
which obtains an image of the current AccessControlContext and then ensures that the
retrieved AccessControlContext contains sufficient permissions for the operation to be
permitted.
If a requested access is allowed, checkPermission returns. If denied, a
SecurityException is thrown.
Permissions fall into categories: File, Socket, Net, Security, Runtime, Property, AWT, Reflect,
and Serializable. The classes managing these various permission categories are as follows:
•
•
•
•
•
•
•
•
•
java.io.FilePermission
java.net.SocketPermission
java.net.NetPermission
java.security.SecurityPermission
java.lang.RuntimePermission
java.util.PropertyPermission
java.awt.AWTPermission
java.lang.reflect.ReflectPermission
java.io.SerializablePermission
See the permissions in the Java 2 SDK permission-related information for additional information.
The document includes a table listing the various SecurityManager check methods and the
permission(s) the default implementation of each such method requires. It also contains a table of
200
all the version 1.2 methods that require permissions, and for each method tells which permission it
requires.
Configuring Basic Security with Policy Files
To configure security in Jini and other Java-based systems, you define a policy file and associate
the file with a process at startup:
java -Djava.security.policy=/usr/policy [rest of command]
The -Djava.security.policy property designates the location and name of the policy
file to associate with the command process.
Policy Files
Permissions are managed in a text file. The default policy file is named java.policy and is
located in your user.home directory. You can determine your system locations, such as
user.home by compiling and running the following short program.
class ListProperties
{
public static void main(String args[])
{
System.out.println("sun.boot.class.path = " + System.getProperty
("sun.boot.class.path"));
System.out.println("java.class.path = " + System.getProperty
("java.class.path"));
System.out.println("user.home = " +
System.getProperty("user.home"));
System.out.println("java.ext.dirs = " + System.getProperty
("java.ext.dirs"));
}
}
A policy file contains a list of entries or directives.
grant [signedBy signers] [,codebase URL] {
permission permission_class [target][,action][,signedBy signers];
permission ...
} ;
•
•
•
•
signers is replaced with the name of the entity or entities that have signed the code.
URL is replaced by the URL address of the location from where the code originated.
permission_class is the class type of the permission, such as
java.net.SocketPermission or java.io.FilePermission.
target is the target identifier of the permission; for instance, the socket number or
•
action is the permitted action of the permission, such as connect, listen, read, write,
filename.
and so on.
If signers is omitted, then code coming from any signer will be granted the permission.
Likewise, a missing URL will grant the permission to code coming from any location. A
201
permission class called AllPermission exists that implies all permissions are granted. As
can be seen, the policy file you have been using in the examples grants all permissions to any code
from anywhere. This is clearly not desirable in a production environment.
Caution
The AllPermission class is intended to be used only in a secure test environment!
grant {
permission java.security.AllPermission "", "";
} ;
Protection Domains
A protection domain is a set of permission entries associated with a code source. The code source
includes a URL of where the code originated, and optionally, the certificates of the entities that
have signed the code. Every Java class is uniquely associated with a code source. When a
untrusted class is loaded, it is mapped to a protection domain based upon its code source and any
signers it might have (see Figure 9.2).
Figure 9.2. The protection domain of an untrusted class.
The grant entries in the policy file describe the permissions granted to the code source. Classes
that have the same permissions but are from different code sources belong to different protection
domains.
For example, to only grant permission to code signed from a specific user from a specific location,
the following code would be used:
grant signedBy "rflenner" codebase "http://www.jworkplace.org" {
permission permission_class [target][,action][,signedBy signers];
permission ...
} ;
This could represent a very restrictive policy, because it specifies both signed user code and code
origination for the permission given.
To grant permission only to code signed from a specific user from any location, you would use
this:
202
grant signedBy "rflenner" {
permission permission_class [target][,action][,signedBy signers];
permission ...
} ;
To grant permission only from a specific location to any code, you would use this:
grant codebase "file:${ java.class.path} " {
permission permission_class [target][,action];
permission ...
} ;
In this last example, you are specifying the local file system class path as the location.
Permissions Relevant to Jini
Of course, all permissions are relevant to Jini; however, there are a few that are notable.
Jini has introduced a new permission class:
net.jini.discovery.DiscoveryPermission. This class signifies permission to
connect to an LUS that supports a particular group. The name can be the "*" to signify all groups,
the empty string to signify the public group, or a specific group name.
permission net.jini.discovery.DiscoveryPermission "*";
Jini relies heavily on the RMI framework. The RMI default server port is 1099; however, if you
are downloading code from the client back to the server (callback), RMI will make connections on
anonymous ports, and therefore requires access to ports greater than the privileged ports starting at
1024. The following gives permission to all hosts (*) at port 1024 and above.
permission java.net.SocketPermission "*:1024-", "connect,accept";
The actions on sockets are accept, connect, listen, and resolve. The action resolve is implied by
accept, connect, and listen—that is, those who can listen or accept incoming connections from a
host or initiate outgoing connections to a host should be able to look up the name of the remote
host.
If you are running your HTTP server on port 8080, an entry of the following would be appropriate:
permission java.net.SocketPermission "*:8080", "connect";
Service discovery and lookup may require the use of the following multicast addresses. See
Chapter 4, "Building on Jini Foundation Concepts," for additional details on multicast addressing.
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
Sample Policy File
Listing 9.1 is the policy file that is used with the reggie implementation of the lookup service.
Listing 9.1 reggie Policy File
/*
203
* This is an example policy file for the lookup service's activation
group.
* You should be able to use this as-is if you use reggie.jar as an
* executable jar file, or if the classpath you use when running
* com.sun.jini.reggie.CreateLookup is just a single jar file or a
single
* directory with a trailing slash. Otherwise you need to replace the
* codebase string below. If you want the lookup database stored
somewhere
* other than in /tmp/reggie_log/, then change that as well.
*/
grant codebase "file:${ java.class.path} " {
permission java.io.FilePermission "/tmp/reggie_log",
"read,write,delete";
permission java.io.FilePermission "/tmp/reggie_log/-",
"read,write,delete";
// uncomment this one if you need lookup to accept file: codebases
// permission java.io.FilePermission "<<ALL FILES>>", "read";
permission java.lang.RuntimePermission "modifyThreadGroup";
permission java.lang.RuntimePermission "modifyThread";
permission java.net.SocketPermission "*:1024-", "connect,accept";
// for http: codebases
permission java.net.SocketPermission "*:80", "connect";
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
permission java.util.PropertyPermission "java.rmi.server.hostname",
"read";
permission java.util.PropertyPermission "com.sun.jini.reggie.*",
"read";
permission java.util.PropertyPermission "net.jini.discovery.*",
"read";
permission net.jini.discovery.DiscoveryPermission "*";
// for transient case only
permission java.lang.RuntimePermission "getContextClassLoader";
permission java.lang.RuntimePermission "setContextClassLoader";
} ;
The code is unsigned, but specifies a specific location (the class path) to grant permissions for this
domain. File permissions are enabled for logging; runtime permissions are enabled for thread
management. Socket permissions enable HTTP connections and multicast discovery. The key to
understanding this file lies primarily in the socket permissions that are granted. The discovery
process requires multicast addresses (224… ), and RMI uses anonymous ports above 1024. In
addition, the HTTP server needs to be set to the port running your HTTP server.
There are other sample policy files that come with the Jini distribution that can help you determine
the settings required.
Java-based Security Tools
There are three security-related tools in the Java distribution that enable you to configure security
for code signing and policy files. You use these tools in conjunction with the jar utility to create
signed code and associated policy declarations. The tools are in the java/bin directory and all
can be invoked from the command line.
keytool
The keytool command line utility creates pairs of public and private keys, and imports and
exports signed (X509) certificates into a keystore. A keystore is a protected database that
204
contains the trusted certificate entries and the key/certificate entries, each containing a private key
and the corresponding public key certificate.
The keytool utility can be used to:
•
•
•
•
•
Create private keys and their associated public key certificates
Issue certificate requests, which you send to the appropriate certification authority
Import certificate replies, obtained from the certification authority you contacted
Import public key certificates belonging to other parties as trusted certificates
Manage your keystore
The private keys are used to digitally sign your code. The public keys are used to verify the signed
code and certificates verify that the public key belongs to the signer.
Code Signing
The jarsigner utility is used to sign and verify the signature of jar files. The jarsigner
accesses the keystore to find:
•
•
•
The private key when signing a jar file
The public key when verifying a signature
The certificate when verifying a public key
policytool
The policytool utility is used to create and modify policy files. It presents a graphical user
interface as opposed to a common text editor approach.
For specific options and use of the Java security tools, consult the Java security documentation.
Additional Security Considerations and Alternative
Techniques
This section provides an overview of security implementation alternatives, starting with the
pervasive presence of firewalls.
Firewalls and HTTP Tunnels
Firewalls are one of the major inhibitors to network traffic. They are used to block and filter
network packets to and from designated TCP/IP addresses and ports. They became extremely
popular with the rise of the World Wide Web, when most corporations started exposing
technology assets over the public network. Unfortunately (or fortunately, depending on your
point-of-view), this has diminished the potential of mobile code and dynamic (multicast)
discovery over the Internet.
To address this issue, RMI uses two popular techniques to bypass common filtering: direct and
indirect forwarding, using HTTP tunnels that encapsulate RMI and the Java Remote Method
Invocation Protocol (JRMP). Figure 9.3 demonstrates indirect forwarding, which requires a cgi
script to be running on the remote server to forward requests to the RMI server. Because this is
still a hit-and-miss proposition without proper system configuration, it has limitations.
205
Figure 9.3. The HTTP tunnel.
Secure Socket Layer
The Secure Socket Layer (SSL) protocol provides encryption, authentication, and integrity of data
by introducing a protocol layer above TCP/IP and defining a handshake process that ensures
communicating peers are authenticated. All data in transit is encrypted to ensure confidentiality.
The Java Secure Sockets Extension (JSSE) supports SSL 3.0 and Transport Layer Security 1.0.
In Chapter 4 you created the JiniShell class. The following code fragments set up your
JiniShell for secure communication using SSL.
First extend JiniShell, which extends UnicastRemoteObject. Change the
constructor to reference an RMISSClientSocketFactory and an
RMISSLServerSocketFactory that you will define.
public class JiniSSLShell extends JiniShell
// create the RMI server using supplied socket factories
public JiniSSLShell() throws IOException {
super(0, new RMISSLClientSocketFactory(), new
RMISSLServerSocketFactory() );
Define a client-side socket factory that implements the RMIClientSocketFactory
interface method createSocket using the SSLSocketFactory.
Listing 9.2 RMISSLClientSocketFactory Class
public class RMISSLClientSocketFactory
implements RMIClientSocketFactory, Serializable {
public Socket createSocket(String host, int port) throws
IOException
{
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
return socket;
}
}
Define a server-side socket factory that implements the RMIServerSocketFactory
interface method createServerSocket using the SSLServerSocketFactory.
206
Listing 9.3 RMISSLServerSocketFactory Class
public class RMISSLServerSocketFactory implements
RMIServerSocketFactory, Serializable {
public ServerSocket createServerSocket(int port) throws IOException
{
SSLServerSocketFactory ssf = null;
try {
// set up key manager to do server authentication
SSLContext ctx;
KeyManagerFactory kmf;
KeyStore ks;
// get an instance of the SSL factory that supports TLS
ctx = SSLContext.getInstance("TLS");
// generate a KeyManagerFactory that implements the X509
algorithm
kmf = KeyManagerFactory.getInstance("SunX509");
// get the default Java KeyStore
ks = KeyStore.getInstance("JKS");
// load the keystore from the designated file using password
char[] passphrase = "passPhrase".toCharArray();
ks.load(new FileInputStream("testkeys"), passphrase);
// initialize the factory with the supplied keystore
kmf.init(ks, passphrase);
// initialize the context with the factory key managers
ctx.init(kmf.getKeyManagers(), null, null);
// get the server socket factory for this context
ssf = ctx.getServerSocketFactory();
}
catch (Exception e) {
e.printStackTrace();
}
// now return our server socket
return ssf.createServerSocket(port);
}
}
Instances of the SSLContext class represent a secure socket protocol implementation, which
acts as a factory for secure socket factories.
The KeyManagerFactory class acts as a factory for key managers based on a source of key
material. Each key manager manages a specific type of key material for use by secure sockets. The
key material is based on a keystore and/or provider-specific sources.
The JSSE was an optional package (extension) prior to JDK 1.4, and was a separate download
from the core distribution. As of JDK 1.4, JSSE has been integrated into the core platform.
Java Authentication and Authorization Service (JAAS)
The Java Authentication and Authorization Service (JAAS) is designed to provide a framework
and standard programming interface for authenticating users and for assigning privileges.
207
Together with Java 2, an application can provide code-centric access control, user-centric access
control, or a combination of both.
This is an important framework for the future of Jini security. It is not a service in the sense of a
Jini service—it does not register with the lookup service—but it will be critical to providing a
framework for security. JAAS defines a number of core classes that are central to providing
authentication and authorization. As mentioned at the start of this chapter, authentication confirms
identity, and authorization provides access control.
The JAAS core classes can be broken into 3 categories—common, authentication, and
authorization.
Common Classes
The JAAS common classes are Subject, Principal, and classes associated with
credentials.
Subject and Principal
JAAS uses the term subject to refer to any user of a computing service. Both users and computing
services represent subjects. The term principal represents a name associated with a subject.
Because subjects may have multiple names (potentially one for each service with which it
interacts), a subject comprises a set of principals.
Listing 9.4 Principal Class
public interface Principal {
public String getName();
}
Listing 9.5 Subject Class
public final class Subject {
public Set getPrincipals() {
}
To authorize access to resources, applications first need to authenticate the source of the request.
The JAAS framework defines the term Subject to represent the source of a request. A
Subject may be any entity, such as a person or service. Once authenticated, a
javax.security.auth.Subject is populated with associated identities, or
Principals. A Subject may have many Principals. For example, a person might
have a name Principal ("John Doe") and a SSN Principal ("123-45-6789"), which
distinguish him from other Subjects.
Credentials
A credential contains information used to authenticate the subject to new services. Such
credentials include passwords, Kerberos tickets, and public key certificates. JAAS credentials may
be any type of object. Therefore, existing credential implementations
(java.security.cert.Certificate, for example) can be incorporated into JAAS.
Third party credential implementations may also be plugged into the JAAS framework.
JAAS divides each Subject's credentials into two sets. One set contains the Subject's
public credentials (public key certificates, Kerberos tickets, and so on). The second set stores the
Subject's private credentials (private keys, encryption keys, passwords, and so on). To access a
208
Subject's public credentials, no permissions are required. However, access to a Subject's
private credential set is security-checked.
Listing 9.6 Subject
...
public Set getPublicCredentials() { } // not security checked
public Set getPrivateCredentials() { } // security checked
}
To modify or operate upon a Subject's Principal Set, public credential Set, or private
credential Set, callers use the methods defined in the java.util.Set class. The following
example demonstrates this:
Subject subject;
Principal principal;
Object credential;
// add a Principal and credential to the Subject
subject.getPrincipals().add(principal);
subject.getPublicCredentials().add(credential);
Authentication Classes
The JAAS authentication classes—LoginContext, LoginModule,
CallbackHandler, and Callback—are central to authentication.
Pluggable and Stackable Authentication (PAM)
The JAAS authentication framework is based on Pluggable and Stackable Authentication (PAM),
and therefore supports an architecture that enables system administrators to plug in the appropriate
authentication services to meet their security requirements. The architecture also enables
applications to remain independent from the underlying authentication services.
The JAAS LoginContext class represents a Java implementation of the PAM framework.
The LoginContext consults a configuration that determines the authentication service, or
LoginModule, that gets plugged in under that application.
Listing 9.7 LoginContext Class
public final class LoginContext {
public LoginContext(String name) { }
public void login() { }
// two phase process
public void logout() { }
public Subject getSubject() { }
// get the authenticated
Subject
}
Listing 9.8 LoginModule Class
public interface LoginModule {
boolean login(); // 1st authentication phase
boolean commit(); // 2nd authentication phase
boolean abort();
boolean logout();
}
209
JAAS, like PAM, supports the notion of stacked LoginModules. To guarantee that either all
LoginModules succeed or none succeed, the LoginContext performs the authentication
steps in two phases. In the first phase, or the 'login' phase, the LoginContext invokes the
configured LoginModules and instructs each to attempt the authentication. If all the necessary
LoginModules successfully pass this phase, the LoginContext then enters the second
phase and invokes the configured LoginModules again, instructing each to formally 'commit'
the authentication process. During this phase, each LoginModule associates the relevant
authenticated principals and credentials with the subject. If either the first phase or the second
phase fails, the LoginContext invokes the configured LoginModules and instructs each
to 'abort' the entire authentication attempt. Each LoginModule then cleans up any relevant
state it had associated with the authentication attempt.
To authenticate a Subject, the following steps are performed:
1. An application instantiates a LoginContext.
2. The LoginContext consults a Configuration to load all of the
LoginModules configured for that application.
3. The application invokes the LoginContext's login method.
4. The login method invokes all of the loaded LoginModules. Each
LoginModule attempts to authenticate the Subject. Upon success, the
LoginModules associate relevant Principals and credentials with the Subject.
5. The LoginContext returns the authentication status to the application.
6. If authentication succeeds, the application retrieves the authenticated Subject from the
LoginContext.
The following code sample demonstrates the calls necessary to authenticate and logout a
Subject:
// let the LoginContext instantiate a new Subject
LoginContext lc = new LoginContext("JWorkPlace");
try {
// authenticate the Subject
lc.login();
System.out.println("authentication successful");
// get the authenticated Subject
Subject subject = lc.getSubject();
...
// all finished -- logout
lc.logout();
} catch (LoginException le) {
System.out.println("authentication
unsuccessful"+le.printStackTrace());
}
To instantiate a LoginModule, a LoginContext expects each LoginModule to
provide a public constructor that takes no arguments. Then, to initialize a LoginModule with
210
the relevant information, a LoginContext calls the LoginModule's initialize
method.
void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options);
The following method begins the authentication process:
boolean login() throws LoginException;
The following method completes and finalizes the authentication process:
boolean commit() throws LoginException;
If Phase 1 of the authentication process was successful, then this method continues with Phase 2—
associating relevant Principals, public credentials, or private credentials with the Subject.
If Phase 1 failed, then this method removes any previously stored authentication state, such as
usernames or passwords.
CallbackHandler is an interface with one method to implement:
void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException;
Underlying security services make requests for different types of information by passing
individual Callbacks to the CallbackHandler. The CallbackHandler
implementation decides how to retrieve and display information depending on the Callbacks
passed to it. For example, if the underlying service needs a username and password to authenticate
a user, it uses a NameCallback and PasswordCallback. The CallbackHandler
can then choose to prompt for a username and password serially, or to prompt for both in a single
window:
public interface Callback { }
Callback implementations simply provide the means to pass requests to applications, and for
applications, if appropriate, to return requested information back to the underlying security
services.
Upon successful authentication of a Subject, fine-grained access controls can be placed upon
that Subject by invoking the Subject.doAs methods. The permissions granted to that
Subject are configured in a JAAS Policy.
Authorization Classes
The JAAS authorization classes Policy, AuthPermission, and
PrivateCredentialPermission are central to authorization.
Policy is an abstract class for representing the system policy for Subject-based
authorization. A subclass implementation of this class provides a means to specify an access
control policy. An AuthPermission class is for authentication permissions. An
AuthPermission contains a name (also referred to as a "target name") but no actions list;
you either have the named permission or you don't.
211
The PrivateCredentialPermission class is used to protect access to private
credentials belonging to a particular Subject. The Subject is represented by a Set of
Principals.
After authentication has successfully completed, JAAS provides the capability to enforce access
controls on the principals associated with the authenticated subject. The JAAS principal-based
access controls supplement the existing Java 2 codesource-based access controls.
Principal-Based Access Control
The following example depicts a codesource-based policy entry currently supported by the default
policy provided with Java 2. This entry grants code loaded from foo.com, and signed by foo;
permission to read all files in the cdrom directory and its subdirectories. Because no principal
information is included with this policy entry, the code will always be able to read files from the
cdrom directory, regardless of who executes it.
grant Codebase "http://foo.com", Signedby "foo" {
permission java.io.FilePermission "/cdrom/-", "read";
}
The next example is a principal-based policy entry supported by JAAS. This sample entry grants
code loaded from bar.com, signed by bar, and executed by duke; and permission to read only
those files located in the /cdrom/duke directory. To be executed by duke, the Subject
affiliated with the current access control context must have an associated principal of class
bar.Principal, whose getName method returns duke. Note that if the code from
bar.com, signed by bar, ran standalone (it was not executed by duke), or if the code was
executed by any principal other than duke, then it would not be granted the
FilePermission. Also note that if the JAAS policy entry did not specify the Codebase
or Signedby information, then the entry's FilePermission would be granted to any code
running as duke.
grant Codebase "http://bar.com, Signedby "bar", Principal
bar.Principal "duke" {
permission java.io.FilePermission "/cdrom/duke/-", "read"; }
Access Control Implementation
The Java 2 runtime enforces access controls via the java.lang.SecurityManager, and
is consulted anytime untrusted code attempts to perform a sensitive operation (access the local file
system, for example). To determine whether the code has sufficient permissions, the
SecurityManager implementation delegates responsibility to the
java.security.Access Controller, which first obtains an image of the current
AccessControlContext, and then ensures that the retrieved
AccessControlContext contains sufficient permissions for the operation to be permitted.
JAAS supplements this architecture by providing the method Subject.doAs to dynamically
associate an authenticated subject with the current AccessControlContext. Hence, as
subsequent access control checks are made, the AccessController can base its decisions
on both the executing code itself, and the principals associated with the Subject.
public final class Subject {
// associate the subject with the current
// AccessControlContext and execute the action
public static Object doAs(Subject s,
java.security.PrivilegedAction action) { }
212
}
The doAs implementation associates the Subject with the current
AccessControlContext and then executes the action. When security checks occur during
execution, the Java 2 SecurityManager queries the JAAS policy, updates the current
AccessControlContext with the permissions granted to the Subject and the
executing codesource, and then performs its regular permission checks. When the action finally
completes, the doAs method simply removes the subject from the current
AccessControlContext and returns the result back to the caller.
To associate a subject with the current AccessControlContext, the doAs method uses
an internal JAAS implementation of the java.security.DomainCombiner interface,
newly introduced in version 1.3 of the Java 2 SDK. It is through the JAAS DomainCombiner
that the existing Java 2 SecurityManager can be instructed to query the JAAS policy
without requiring modifications to the SecurityManager itself. Details of the interaction
between the Java 2 SecurityManager and DomainCombiners are documented in the
documentation for the java.security.DomainCombiner interface.
JAAS Example
The following sample configuration file defines a single login module.
/** Login Configuration for JWorkPlace **/
JWorkPlace {
org.jworkplace.login.WorkPlaceLoginModule required debug=true;
} ;
A login module can be associated with a service by setting the property:
Djava.security.auth.login.config==/usr/JiniServices/CMS/config/login.
config
You will need to grant the necessary security permissions to your codebase. If you are using JDK
1.3, then the JAAS is not part of the core distribution and therefore would require a grant
statement similar to the following:
/** Java 2 Access Control Policy for the JAAS Application **/
/* grant the JAAS core library AllPermission */
grant codebase "file:/usr/java/jdk1.3/jre/lib/ext/jaas.jar" {
permission java.security.AllPermission;
} ;
/* grant the sample LoginModule AllPermission */
grant codebase "file:/usr/JWorkPlace/lib/login.jar" {
permission java.security.AllPermission;
} ;
grant codebase "file:/usr/JWorkPlace/lib/service.jar" {
permission
permission
permission
permission
javax.security.auth.AuthPermission "createLoginContext";
javax.security.auth.AuthPermission "doAs";
java.util.PropertyPermission "java.home", "read";
org.jworkplace.login.AccountPermission "createAccount";
213
} ;
A login policy can be associated with a service by setting the property:
-Djava.security.policy==/usr/JiniServices/CMS/config/login.policy
The following policy extends the Java 2 codebase policy with Subject-based access control.
This grant entry provides the necessary permissions to perform a sensitive operation provided in
the service.jar to any Subject that has an associated WorkPlacePrincipal.
/** Subject-Based Access Control Policy for the JWorkPlace
Application **/
grantcodebase "file:/usr/JWorkPlace/lib/service.jar",
Principal org.jworkplace.login.WorkPlacePrincipal * {
permission org.jworkplace.login.AccountPermission "createAccount";
} ;
A login authorization policy can be associated with a service by setting the property:
Djava.security.auth.policy==/usr/JiniServices/CMS/config/login_jaas.p
olicy
The following LoginHandler class implements the CallbackHandler interface.
Listing 9.9 LoginHandler Class
package org.jworkplace.login;
import
import
import
import
import
import
import
import
java.io.*;
java.util.*;
java.security.Principal;
javax.security.auth.*;
javax.security.auth.callback.*;
javax.security.auth.login.*;
javax.security.auth.spi.*;
com.sun.security.auth.*;
public class LoginHandler implements CallbackHandler {
LoginContext lc;
NameCallback nameCallback;
PasswordCallback passwordCallback;
int loginTries = 0;
String user;
char[] password;
// create the LoginContext using the JWorkPlace configuration
public LoginHandler() {
try {
lc = new LoginContext("JWorkPlace", new Subject(), this);
} catch (LoginException le) {
le.printStackTrace();
System.exit(-1);
}
}
214
// The Callback Interface
// provides the types of callbacks used to interface with the
LoginModule
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " +
toc.getMessageType());
}
}
else if (callbacks[i] instanceof NameCallback) {
nameCallback = (NameCallback)callbacks[i];
nameCallback.setName(user);
}
else if (callbacks[i] instanceof PasswordCallback) {
passwordCallback = (PasswordCallback)callbacks[i];
passwordCallback.setPassword(password);
}
else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
// user defined login method which simply counts the number of login
attempts
// and sets the user and password which will be used in the callback
above
public boolean login(String user, char[] password)
{
if(loginTries > 2) {
System.out.println("Sorry charlie...");
return false;
} else {
this.user = user;
this.password = password;
}
loginTries++;
try {
// attempt authentication on the LoginContext
lc.login();
215
// if we return with no exception, authentication succeeded
return true;
}
catch (AccountExpiredException aee) {
System.out.println("Your account has expired. " +
"Please notify your administrator.");
}
catch (CredentialExpiredException cee) {
System.out.println("Your credentials have expired.");
}
catch (FailedLoginException fle) {
System.out.println("Authentication Failed");
}
catch (Exception e) {
System.out.println("Unexpected Exception - unable to
continue");
e.printStackTrace();
}
// authentication did not succeed
return false;
}
}
The following WorkPlacePrincipal class implements the Principal interface.
Listing 9.10 WorkPlacePrincipal Class
package org.jworkplace.login;
import java.security.Principal;
public class WorkPlacePrincipal implements Principal,
java.io.Serializable {
private String name;
public WorkPlacePrincipal(String name) {
if (name == null)
throw new NullPointerException("illegal null input");
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return("WorkPlacePrincipal: " + name);
}
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof WorkPlacePrincipal))
return false;
216
WorkPlacePrincipal that = (WorkPlacePrincipal)o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
public int hashCode() {
return name.hashCode();
}
}
The following WorkPlaceLoginModule class implements the LoginModule interface.
The specific validation of user/password is not shown. You would provide your specific
implementation.
Listing 9.11 WorkPlaceLoginModule Class
package org.jworkplace.login;
import
import
import
import
import
import
java.util.*;
java.io.IOException;
javax.security.auth.*;
javax.security.auth.callback.*;
javax.security.auth.login.*;
javax.security.auth.spi.*;
public class WorkPlaceLoginModule implements LoginModule {
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// configurable option
private boolean debug = false;
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
// username and password
private String username;
private char[] password;
private WorkPlacePrincipal userPrincipal;
// the initialize method is called by the LoginContext
public void initialize(Subject subject, CallbackHandler
callbackHandler,
Map sharedState, Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
}
217
// this is the login method that is called by the LoginContext
public boolean login() throws LoginException {
// prompt for a username and password
if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available
");
// Two Callback are created, one for the name and one for the
password
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("JWorkPlace username: ");
callbacks[1] = new PasswordCallback("JWorkPlace password: ",
false);
try {
// this is the callback to our LoginHandler
callbackHandler.handle(callbacks);
// get the username and password provided in the callback
username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword =
((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword == null) {
// treat a NULL password as an empty password
tmpPassword = new char[0];
}
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0,
password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
}
catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " +
uce.getCallback().toString());
}
// verify the username/password
// insert code to verify user / password here
// Authentication succeeded
succeeded = true;
return true;
// Authentication failed
succeeded = false;
throw new FailedLoginException("Password Incorrect");
}
// the 2 phases of authentication - success commit
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
// add a Principal (authenticated identity)
// to the Subject
// assume the user we authenticated is the WorkPlacePrincipal
userPrincipal = new WorkPlacePrincipal(username);
if (!subject.getPrincipals().contains(userPrincipal)) {
subject.getPrincipals().add(userPrincipal);
218
}
// clean out state
username = null;
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
commitSucceeded = true;
return true;
}
}
// the 2 phases of authentication - failure abort
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
succeeded = false;
succeeded = commitSucceeded;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
return true;
}
}
The following WorkPlaceAction class implements the PrivilegedAction interface.
The PrivilegedAction interface is implemented by classes that require access control
checks to be performed. The run method is called by
AccessController.doPrivileged after enabling privileges.
Listing 9.12 The WorkPlaceAction Class
package org.jworkplace.login;
import java.io.File;
import java.security.PrivilegedAction;
219
public class WorkPlaceAction implements PrivilegedAction {
// run method defined in the PrivilegedAction interface
public Object run() {
// get the security manager and check if permission has been
granted
SecurityManager sm = System.getSecurityManager();
sm.checkPermission(new AccountPermission("createAccount"));
return null;
}
}
Finally, the AccountPermission class extends BasicPermission.
BasicPermission provides a simple base class for creating new permission types. In this
case, you create a new AccountPermission class that is checked in the
WorkPlaceAction to verify that the thread of control associated with the current Subject
has the necessary permission to create an account.
package org.jworkplace.login;
import java.security.BasicPermission;
public final class AccountPermission extends BasicPermission
implements java.io.Serializable
{
public AccountPermission(String name) {
this(name,null);
}
public AccountPermission(String name, String action) {
super(name);
}
}
In Need of Evolution
As you recall, the security requirements and architecture required to support environments like
RMI and Jini are difficult to standardize and control. The Davis Project is being developed to
address these security issues (see www.jini.org/projects).
The Davis Project
The Davis Project is an effort by the Jini technology project team at Sun Microsystems to address
the need for a Jini technology security architecture, as well as to contribute other improvements to
Jini technology. The Davis Project will ultimately result in a Jini technology starter kit release
from Sun, following the Alewife 1.2 release. In the short run, the Davis Project has produced the
Overture release. Currently available for download, Overture includes the basic security
architecture needed to support further efforts.
The Overture Release
As stated in the release notes, Overture contains the following components:
•
A network security programming model and API for remote calls.
220
•
•
•
•
•
•
•
•
•
A programming model and API for exporting remote objects.
A configuration programming model and API for deployment-time customization of
applications without modifying code.
An implementation (on par with JRMP and RMI/IIOP) of the RMI programming model
called Jini extensible remote invocation, which supports the export programming model
and provides flexible customization of remote communication, including invocation
semantics and transport protocols.
An implementation of the RMI programming model called secure Jini extensible remote
invocation, which supports the network security programming model in addition to the
other features supported by non-secure Jini extensible remote invocation.
TCP and HTTP transport providers for Jini extensible remote invocation, and SSL and
HTTPS transport providers for secure Jini extensible remote invocation.
Implementations of the export programming model for JRMP and RMI/IIOP.
An RMIClassLoaderSpi provider implementation that enhances codebase
annotation retention, supports customized codebase annotations, and provides control
over what code can be dynamically downloaded.
Extensions to the RMI activation framework to support the network security and export
programming models.
An RMI activation system daemon implementation named phoenix that supports and uses
the network security, configuration, and export programming models as well as the
activation extensions.
The Davis release will further enhance the security architecture and environment that Jini is
capable of supporting. This should provide a catalyst for the use of Jini in new environments,
including the Internet.
Summary
Jini requires standard network authentication-type security. In addition, Jini needs to provide an
environment that supports standard security processes for mobile and downloaded code.
The diversity of systems, security standards, deployment alternatives, and the complexity of
integration have impeded progress in defining security architecture.
However, Java security has been evolving and changing at a rapid pace. Access control has
evolved to be far more fine-grained with each version of the Java platform. Access control
decisions can now be based both on the executing code's CodeSource, as well as on the user
running the code, or the Subject.
The Java Virtual Machine (JVM), from a security perspective, provides the sandbox that ensures
code runs in a protected space, and has limited access to system resources. The key components in
the JVM security framework are the following:
•
•
•
Class loader
Class file verifier
Security manager
The security manager is responsible for the runtime restrictions that are imposed on code after the
code has gone through the verification process. The security manager enforces restrictions based
on security policy statements.
221
There are security-related tools in the Java distribution that enable you to configure security for
code signing and policy files.
Firewalls are one of the major inhibitors to network traffic. They are used to block and filter
network packets to and from designated TCP/IP addresses and ports. RMI uses two popular
techniques to bypass common filtering—direct and indirect forwarding, using HTTP tunnels that
encapsulate RMI and the Java Remote Method Invocation Protocol (JRMP).
The Java Authentication and Authorization Service (JAAS) is designed to provide a framework
and standard programming interface for authenticating users and for assigning privileges.
Together with Java 2, an application can provide code-centric access control, user-centric access
control, or a combination of both. This is an important framework for the future of Jini security.
A major development area in the next release of Jini—The Davis Project—is an effort to address
the need for a Jini technology security architecture.
222
Part III: Developing Applications
IN THIS PART
10 GroupWare
11 Workflow
12 Agents
13 Jini as a Web Service
14 Pervasive Computing and Mobile Devices
223
Chapter 10. GroupWare
IN THIS CHAPTER
•
•
•
•
•
•
Defining the JWorkPlace
The Role of XML in Workplace Definition
The Supporting Services: Reggie, Outrigger, and Mahalo
Client-Side Service Discovery
Introducing the ServiceUI Project
Summary
This chapter provides practical applications and examples of the Jini and JavaSpaces concepts
introduced in the prior two sections. You will develop a sample GroupWare application that
includes the following:
•
•
•
•
Content management service
Instant messaging service
Account management service
Visual User Interface techniques
The GroupWare example is based on JWorkPlace, which is my original creation. Visit
www.jworkplace.org.
Defining the JWorkPlace
GroupWare applications are becoming more important to businesses and individuals due to the
explosion of digital commerce linkage ushered in by the Internet economy. Initially, the term
"GroupWare" was used to refer to integrated e-mail and calendar/scheduling software but has
evolved to include other functions, such as instant messaging, file sharing, workflow support, and
voice/video over IP.
The peer-to-peer (P2P) revolution extended GroupWare from the corporate closet to now include
the Internet at large. This will have a radical impact on future collaboration possibilities.
Individuals and businesses will no longer be bound by specific tools within a community or
geographic region. Common interests and objectives will be the driving force. The tools to support
the formation and integration of decentralized communication and ad hoc group formation already
exist. For example, the popularity of AOL Instant Messenger and ICQ have demonstrated that
real-time communication is a viable and often preferred way to communicate. Products such as
Napster and FreeNet have proven that decentralized file sharing is possible.
Jini and JavaSpaces provide an excellent technology choice to meet this demand for decentralized
and ad hoc group formation—Jini with its inherent capability to discover services and devices on a
network, and JavaSpaces with its ease in supporting network rendezvous points. Together they
provide the necessary infrastructure to build the next generation of GroupWare applications.
In this chapter, you turn your attention from service building blocks to service application. In
other words, you take the core services that you have been learning and apply them to produce
applications that can serve as a foundation for further exploration.
The first application to explore is JWorkPlace.
224
The Role of XML in Workplace Definition
JWorkPlace was created based on the idea that code fragments (snippets) are often exchanged by
developers when developing software projects. Although class libraries and frameworks offer
more complete solutions, the developer many times requires only a portion of code to complete a
coding effort. The advantage to working with snippets is that fragments are easy to learn and
provide a solution focused on a specific requirement. However, the downside is that they tend to
be randomly constructed and lack any management in the development process.
JWorkPlace was designed to allow code exchange between development team members. In
addition, by integrating instant messaging, it provides real-time communication capabilities.
Code fragments in the JWorkPlace are defined using XML. A simple code fragment might be
defined as
<fragment author="anonymous" text="
private static class ShutdownThread extends Thread {
private JoinManager joinManager;
public ShutdownThread(JoinManager joinManager) {
this.joinManager = joinManager;
}
public void run() {
joinManager.terminate();
return;
}
}"
</fragment>
A more complete definition of XML might provide information relative to code usage or where
detailed information might be located (URL reference). More structure could be added by
classifying code fragments within categories, such as usage or source of code definition.
The fundamental idea is that development team members can improve on the common cut, copy,
paste, and e-mail process that exists today.
You are going to extend the basic definition of an XML-based code fragment repository to apply
to a more general content management and collaboration platform using Jini and JavaSpaces. The
content management system is going to be defined using P2P techniques, as shown in Figure 10.1.
Figure 10.1. Jini and JavaSpaces can provide a platform for peer-to-peer discovery
and information exchange.
225
Your P2P content management system will first register with a Jini lookup service. A using entity
will create an account through the account service to gain access to the content system.
Establishing an account allows you to publish a portion of your computer's directory to the shared
content system. As a result, you can add files—even though they remain on your PC—to the
shared repository. If you have given access to a file to another member in your group, that
member can transfer your file to his local machine from your machine. In addition, all group
members can share and exchange files located within the group repository.
You use Jini as the mechanism to discover the services that comprise the workplace and use
JavaSpaces as a point of rendezvous.
The Supporting Services: reggie, outrigger, and mahalo
Let's look at the supporting services required to build your workplace.
In addition to the lookup service, your workplace requires JavaSpaces and the transaction service.
JavaSpaces provides a rendezvous point to view content available for exchange. A rendezvous
point provides a meeting place on the network. There, you will provide messaging capabilities,
event notifications, and content space definitions you want to share with other group members, as
seen in Figure 10.2.
Figure 10.2. JWorkPlace builds on Jini and JavaSpaces by providing applicationspecific content, account, and messaging capabilities.
226
Account Service
AccountManager provides the interface to administering accounts in JWorkPlace. Note that
it extends Administrable, so it defines the getAdmin method.
Listing 10.1 The AccountManager Interface
package org.jworkplace.account;
import java.rmi.RemoteException;
import java.io.IOException;
import net.jini.admin.Administrable;
public interface AccountManager extends Administrable {
public Account getAccount(String user) throws RemoteException;
public Account[] getAccounts() throws RemoteException;
public void create(Account account) throws RemoteException;
public void update(Account account) throws RemoteException;
public void remove(String userName) throws RemoteException;
}
To access JWorkPlace, you must have an account. An account contains the following information:
Listing 10.2 The Account Class
package org.jworkplace.account;
import net.jini.entry.AbstractEntry;
public class Account extends AbstractEntry {
public String user;
public String password;
public String name;
public String location;
public String primaryHome;
public String secondaryHome;
public Account()
{
super(); }
}
The Account class also extends AbstractEntry, so your accounts are capable of being
written to and read from JavaSpaces.
The Account class contains the familiar user ID, password, and username properties. In
addition it contains reference to a primary and secondary home. The primary and secondary
227
homes are locations where you have registered your interest in a group at JWorkPlace. Location
refers to your current IP address.
When you create an account, the AccountService persists the account information. As
shown in Figure 10.3, AccountService implements the RemoteAccountManager
and ServiceAdmin interface and extends the ServiceImpl class defined in Chapter 8,
"Service Administration."
Figure 10.3. AccountService extends the abstract ServiceImpl class defined
in Chapter 8.
public class AccountService extends ServiceImpl implements
ServiceAdmin, RemoteAccountManager
{
private Map dataMap;
The dataMap is a HashMap used to index accounts. You need to wrap the HashMap or
synchronize on some object if you are going to allow multiple threads to change the structure of
the map—add/remove mappings. You can use Collections.synchronizedMap to
return a synchronized (thread-safe) map backed by the specified map:
Map m = Collections.synchronizedMap(new HashMap(...));
The methods within the AccountService simply access the dataMap by mapping users to
Account objects through get and put methods.
Content Service
IndexManager provides the interface to the content management service:
Listing 10.3 The IndexManager Interface
package org.jworkplace.content;
import java.rmi.RemoteException;
import java.io.IOException;
import net.jini.admin.Administrable;
public interface IndexManager extends Administrable
{
public void addContentSpace(String user, String localPath,
String contentSpace)
228
public void removeContentSpace(String user,
String localPath, String contentSpace)
public SpaceIndex[] list(String user, String contentSpace)
public ContentMap[] getContentSpace(String user)
public void addContent(String user, String contentSpace, Content
content)
public void removeContent(String user, String contentSpace,
Content
content)
public Content getContent(String user, String contentSpace,
String name)
public Content[] getContent(String user, String contentSpace )
public void shareContent(String guest, String owner,
String localPath, String contentSpace)
public void removeContentShare(String guest, String owner,
String localPath, String contentSpace)
}
The ContentManager implements the IndexManager interface and provides support for
creating a space (see Figure 10.4). A space is a shared repository that enables group members to
view and exchange files.
Figure 10.4. The ContentService extends the abstract ServiceImpl class defined
in Chapter 8. It implements the IndexManager, ServiceAdmin, and the Jini
DiscoveryListener interfaces.
Note
The space is actually an index to files located on group member machines. When you create a
space, you are merely naming an index that you share with other group members. The files are
never moved to a central location. They are accessed peer-to-peer.
The ContentManager provides the support necessary to use the JavaSpaces service to read
and write SpaceIndex entries using the JavaSpace API.
public class ContentManager extends ServiceImpl implements
DiscoveryListener,
ServiceAdmin,
RemoteIndexManager
229
{
private
private
private
private
Map spaceMap;
Map userMap;
Map groupMap;
JavaSpace space;
The spaceMap provides a mapping between user-defined content space names and files in that
named content space.
The userMap provides a mapping between users and a list of owned content space names.
The groupMap provides a mapping between users and a list of nonowned shared content space
names.
Defining and Sharing Content
The Content interface provides the mapping of a name to a location. Specific types, such as
file content, would implement this interface.
Listing 10.4 The Content Interface
package org.jworkplace.content;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.net.URL;
public interface Content extends Serializable
{
public String getLocation() throws RemoteException;
public String getName() throws RemoteException;
}
The returned name and location are content type-specific. For instance, the String of type
FileContent is a String representation of a URL. The String of type
SpaceContent is a reference to a Space location. The Space class wraps the
LookupLocator defined by Jini. The LookupLocator does not use a valid URL as
defined by the net.java.URL class.
Listing 10.5 The SpaceContent Class
package org.jworkplace.content;
import java.net.URL;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
public class SpaceContent implements Content
{
public final String protocol = "jini";
public Space space;
public SpaceContent(String host, int port, String contentSpace) {
space = new Space(protocol, host, port, contentSpace);
}
230
public String getLocation() throws RemoteException {
return space.toString();
}
public String getName() throws RemoteException {
return space.getName();
}
}
Listing 10.6 The FileContent Class
package org.jworkplace.content;
import java.net.URL;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
public class FileContent implements Content
{
public URL url;
public final String protocol = "http";
public FileContent(String host, int port, String file)
throws MalformedURLException {
url = new URL(protocol, host, port, file);
}
public String getLocation() throws RemoteException {
return url.toString();
}
public String getName() throws RemoteException {
return url.getFile();
}
}
You use HTTP to transfer files between user machines. An HTTP server will typically be running
on your machine. You can use the Jini-supplied HTTP server used for the examples in this book,
or you can use an HTTP server of your choice.
The following fragment implements the addContentSpace method of the
IndexManager interface:
public void addContentSpace(String user, String localPath,
String contentSpace) throws RemoteException,
IOException
{
try {
if(!userMap.containsKey(user)) {
List contentList = new ArrayList();
userMap.put(user, contentList);
}
List contentList = (List)userMap.get(user);
int index = -1;
index = ( (contentList.lastIndexOf(user) == -1 ) ?
contentList.size() : index+1);
contentList.add(index, new ContentMap(user,
localPath, contentSpace));
spaceMap.put(user+contentSpace, new HashMap());
} catch (Exception e) { e.printStackTrace(); }
231
}
The method checks to see whether the user already has a content list defined. If the user does not,
the method creates a new content list and then adds a ContentMap entry to the list. The
ContentMap contains the mapping between the user, the local directory, and a user-defined
space. In this way, you can organize your files in "spaces" that allow you to break from the
hierarchical organization inherent in systems today. You can define spaces or containers for files
based on other organizational dimensions, such as time, space, or process.
The SpaceIndex class extends Index, which extends AbstractEntry and is used to
define the content space name and an ID that associates the space with a specific file.
Listing 10.7 The SpaceIndex Class
package org.jworkplace.content;
import net.jini.entry.AbstractEntry;
public class SpaceIndex extends Index {
public String contentId;
public SpaceIndex() {
this(null,null);
}
public SpaceIndex(String spaceId, String contentId)
{
this.name = spaceId;
this.contentId = contentId;
}
}
You read and write SpaceIndex entries to an instance of JavaSpaces using the
ContentManager as a proxy to the JavaSpace API. You can therefore run the JavaSpaces
service on a server not directly exposed to any client-side access. The SpaceIndex is merely a
parallel structure to the spaceMap hash map. You can use this structure to easily integrate event
notification and messaging into the workplace environment.
The following fragment implements the shareContent method of the IndexManager
interface:
public void shareContent(String guest, String owner, String localPath,
String contentSpace) throws RemoteException,
IOException
{
if(!groupMap.containsKey(guest)) {
List contentList = Collections.synchronizedList(new
ArrayList());
groupMap.put(guest, contentList);
}
List contentList = (List)groupMap.get(guest);
int index = -1;
index = ( (contentList.lastIndexOf(guest) == -1 ) ?
contentList.size() : index+1);
contentList.add(index, new ContentMap(owner, localPath,
contentSpace));
}
232
The method checks to see whether the guest already has a content list defined. This method would
be invoked to grant file access to another user as specified by the guest parameter. Again the
ContentMap contains the mapping between the user, the local directory, and a user-defined
space. However, this content map is associated with a guest as opposed to the owner. When you
determine access to content you concatenate the user and group content lists.
public ContentMap[] getContentSpace(String user) throws
RemoteException, IOException
{
List userSpaceList = (List)userMap.get(user);
if(userSpaceList == null)
return new ContentMap[0];
ArrayList contentList = new ArrayList(userSpaceList);
if(groupMap.containsKey(user)) {
List groupContent = (List)groupMap.get(user);
contentList.addAll(groupContent);
}
return (ContentMap[])contentList.toArray(new ContentMap[0]);
}
Messenger Service
The messenger service is a modified version of the chat application developed in Chapter 5,
"JavaSpaces Service." The MessengerInterface provides a simplified wrapper to the
JavaSpace API. The MessengerService implements the MessengerInterface.
Listing 10.8 The MessengerInterface
package org.jworkplace.chat;
import
import
import
import
import
import
java.rmi.RemoteException;
java.rmi.MarshalledObject;
java.io.IOException;
net.jini.core.entry.Entry;
net.jini.core.transaction.TransactionException;
net.jini.core.entry.UnusableEntryException;
public interface MessengerInterface extends java.io.Serializable {
public Entry read(Entry template) throws RemoteException, TransactionException,
UnusableEntryException, InterruptedException;
public Entry readIfExists(Entry template) throws RemoteException,
TransactionException,
UnusableEntryException, InterruptedException;
public void write(Entry entry) throws RemoteException, TransactionException,
UnusableEntryException, InterruptedException;
public void write(Entry entry, long leaseTime) throws RemoteException,
TransactionException, UnusableEntryException, InterruptedException;
public Entry take(Entry entry) throws RemoteException, TransactionException,
UnusableEntryException, InterruptedException;
public Entry takeIfExists(Entry entry) throws RemoteException,
TransactionException,
UnusableEntryException, InterruptedException;
}
233
In the sample JWorkPlace application, channels of communication are associated with active
content space names. In other words, when you activate a content space, you activate a message
channel using JavaSpaces. You can send messages to all members who share or have access to
that space. In addition, there is a default channel (WorkPlaceChannel) that allows you to broadcast
messages to all workplace members.
Figure 10.5. The messenger uses JavaSpaces as a communication channel. It
provides broadcast and shared space messaging capability.
This service will continue to evolve in Chapter 11, "Workflow," and Chapter 12, "Agents."
Client-Side Service Discovery
Now that you have learned about the background on the service side of JWorkPlace, let's move to
the client side and introduce some support for common functions required by any client of a
service.
A common problem that must be addressed on the client side is service discovery. Utilities that
ease the burden of developing from scratch with each service you want to access are nice to have
available. The net.jini.lookup.ServiceDiscoveryManager provides such
support.
You can use the ServiceDiscoveryManager for service discovery support in your
client-side applications. In JWorkPlace, you define two helper classes, ClientStarter and
ClientLookup, that use the ServiceDiscoveryManager (see Figure 10.6).
Figure 10.6. The Jini ServiceDiscoveryManager simplifies finding services for
clients in a Jini community.
234
The ServiceDiscoveryManager
The ServiceDiscoveryManager is used by clients to find specific services in a Jini
community. The ServiceDiscoveryManager (SDM) takes a DiscoveryManager
and a LeaseRenewalManager as parameters—both of which may be null.
The SDM provides a client-side cache that can be used to cache ServiceItems locally and
then allow the client to receive notification when new services are found or when existing services
change. The ClientLookup class that follows illustrates how the
ServiceDiscoveryManager may be used to provide common client-side service
discovery capabilities:
Listing 10.9 The ClientLookup Class
package org.jworkplace.client;
import
import
import
import
import
import
import
import
import
import
import
import
net.jini.core.lookup.ServiceItem;
net.jini.core.lookup.ServiceTemplate;
net.jini.core.lookup.ServiceRegistrar;
net.jini.lookup.ServiceDiscoveryManager;
net.jini.lookup.LookupCache;
net.jini.lookup.ServiceItemFilter;
net.jini.lookup.ServiceDiscoveryListener;
net.jini.discovery.DiscoveryManagement;
net.jini.lease.LeaseRenewalManager;
java.io.IOException;
java.rmi.RemoteException;
java.rmi.RMISecurityManager;
public class ClientLookup {
// the ServiceDiscoveryManager
protected ServiceDiscoveryManager lookupMgr;
public ClientLookup() throws IOException {
this(null, null);
}
public ClientLookup(DiscoveryManagement discMgr,
LeaseRenewalManager leaseMgr) throws IOException
{
if ( System.getSecurityManager() == null ) {
System.setSecurityManager( new RMISecurityManager() );
}
// create the SDM using the passed DiscoveryManager
// and LeaseRenewalManager
lookupMgr = new ServiceDiscoveryManager( discMgr, leaseMgr );
}
235
// Sub-classes of ClientLookup can override to provide
// ServiceDiscoveryListener
public ServiceDiscoveryListener getListener() {
return null;
}
// Sub-classes can override to provide ServiceItemFilter
public ServiceItemFilter getFilter() {
return null;
}
// Sub-class can override on a lookup failure
public boolean requiredFailureAction(ServiceTemplate template) {
System.out.println(template);
return false;
}
// helper method to find a LookupService
public ServiceRegistrar getLookupService() throws RemoteException
{
ServiceRegistrar registrar = null;
Class[] types = { ServiceRegistrar.class } ;
ServiceTemplate tmpl = new ServiceTemplate( null, types,
null );
ServiceItem item = getServiceItem(tmpl);
if(!(item.service instanceof ServiceRegistrar)) {
throw new RemoteException();
}
return (ServiceRegistrar)item.service;
}
// Used to find a service using the tmplate provided
public ServiceItem getServiceItem(ServiceTemplate template) {
ServiceItem service = null;
try {
// instruct SDM to wait no more than 10 seconds to
discover service
service = lookupMgr.lookup( template, getFilter(),
10000 );
} catch ( RemoteException ex ) {
ex.printStackTrace();
} catch ( InterruptedException ex ) { }
if ( service == null ) {
boolean result = requiredFailureAction(template);
if(result) System.exit(1);
}
return service;
}
// Used to find an array of services using the template provided
public ServiceItem[] getServiceItems(ServiceTemplate template) {
ServiceItem[] service = new ServiceItem[0];
try {
service = lookupMgr.lookup( template, 1,
Integer.MAX_VALUE,
236
}
}
getFilter(), 10000 );
catch ( RemoteException ex ) {
ex.printStackTrace();
catch ( InterruptedException ex ) { }
if ( service == null ) {
boolean result = requiredFailureAction(template);
if(result) System.exit(1);
}
return service;
}
// Used to construct a client side cache using the filter if
overridden
public LookupCache createCache(ServiceTemplate template) throws
RemoteException {
return lookupMgr.createLookupCache(template, getFilter(),
getListener());
}
// terminates the discovery process
public void terminate() {
lookupMgr.terminate();
}
}
Remember that the ServiceDiscoveryManager exports a remote
ServiceDiscoveryListener object. The client must have an HTTP server running with
a supplied codebase for the lookup service to access the necessary classes. The lack of a clientside HTTP server has caused some confusion on the deployment of the
ServiceDiscoveryManager because the behavior, if not properly deployed, is to simply
find no services.
The four files necessary to be included in the client-side jar are:
net.jini.core.event.RemoteEvent.class
net.jini.core.event.RemoteEventListener.class
net.jini.core.event.UnknownEventException.class
net.jini.lookup.ServiceDiscoveryManager$LookupCacheImpl$
LookupListener_Stub.class
Knowing this can save you hours of debugging code. Believe me, I know this firsthand!
Listing 10.10 The StartWorkPlace Class
package org.jworkplace.workplace;
import
import
import
import
import
import
import
import
import
import
import
import
java.io.IOException;
java.rmi.Remote;
net.jini.core.lookup.ServiceTemplate;
net.jini.core.lookup.ServiceItem;
net.jini.core.entry.Entry;
net.jini.lookup.entry.Name;
net.jini.lookup.entry.ServiceInfo;
org.jworkplace.chat.MessengerInterface;
org.jworkplace.start.ClientStarter;
org.jworkplace.content.IndexManager;
org.jworkplace.account.AccountManager;
org.jworkplace.client.WorkPlaceClient;
237
public class StartWorkPlace {
private static String starterClass =
StartWorkPlace.class.getName();
// List of services required
private static String contentMgrClass =
IndexManager.class.getName();
private static String accountMgrClass =
AccountManager.class.getName();
private static String spaceMgrClass =
MessengerInterface.class.getName();
private static final int SERVICE_COUNT = 3;
// name of resource file if supplied
private static String resourcename = "service";
// Atrributes of services
private static Entry[] entries = {
new Name("ContentManager"),
new ServiceInfo("JWorkPlace",
"Robert
Flenner",
"Sams",
"1.0","",
"") } ;
// Similar to the server side start-up
public static void main(String[] args) {
// pass the command line arguments
// the name of this class
// the service bootstrap class
// and a resource file name
ClientStarter.create(args, starterClass, contentMgrClass,
resourcename);
}
// this method is invoked by reflection from ClientStarter
public static ServiceTemplate[] createTemplates()
throws ClassNotFoundException
{
try {
{
{
{
Class[] content = new Class[]
Class.forName(contentMgrClass) } ;
Class[] account = new Class[]
Class.forName(accountMgrClass) } ;
Class[] space = new Class[]
Class.forName(spaceMgrClass) } ;
// create the necessary templates to find the services
ServiceTemplate[] templates = new
ServiceTemplate[SERVICE_COUNT];
templates[0] = new ServiceTemplate(null, content, entries);
templates[1] = new ServiceTemplate(null, account, null);
templates[2] = new ServiceTemplate(null, space, null);
return templates;
}
catch (Exception e) {
e.printStackTrace(System.out);
238
throw (ClassNotFoundException)e;
}
}
// this method is called once the services have been discovered
public static void doStart(ServiceItem[] item) {
if(item!= null) {
store(item);
// start the workplace client with the ServiceItems
new WorkPlaceClient(item);
}
}
// override
public static void store(ServiceItem[] item) { }
}
The StartWorkPlace class provides client support for the JWorkPlace application. Most of
the code is boilerplate code that can be declared. It provides an easy way to request multiple
interfaces and then make a determination as to the functionality that the resulting environment can
support. Overriding the store method provides the capability to save information locally, such as
service IDs, service names, and so on.
The ClientStarter class uses the ClientLookup utility to find the necessary
workplace services. The Jini services supplied by Sun Microsystems demonstrate startup
techniques for the Jini services, such as reggie, mahalo, norm, and so on. The ClientLookup
class is modeled after that approach to provide for a more sophisticated client-side startup.
Listing 10.11 The ClientLookup Class
package org.jworkplace.start;
import java.lang.reflect.Method;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;
import org.jworkplace.client.ClientLookup;
public class ClientStarter {
// ServiceDiscoveryManager helper
protected static ClientLookup clientLookup;
// will return the ServiceItem(s) from the templates created
public static void create(String[] args,
String
starterClassName,
String
implClassName,
String
resourcename)
{
Method starterMethod = null;
ServiceTemplate[] template = null;
try {
// create the ServiceDiscoveryManager
clientLookup = new ClientLookup();
239
Class starterClass = Class.forName(starterClassName);
// get the templates required from the client
starterMethod =
starterClass.getMethod("createTemplates",null);
template =
(ServiceTemplate[])starterMethod.invoke(null,null);
// do lookup on each required service interface
Class[] argTypes = { ServiceItem[].class } ;
starterMethod = starterClass.getMethod("doStart",
argTypes);
Object[] methodTypes = {
doLookup(template) } ;
// invoke the doStart method of the client with the
results
starterMethod.invoke(null,methodTypes);
} catch (Exception e) {
e.printStackTrace(System.out);
throw (RuntimeException)e;
}
}
private static ServiceItem[] doLookup(ServiceTemplate template[])
{
ServiceItem[] item = new ServiceItem[template.length];
for(int i=0; i<template.length; i++) {
item[i] = clientLookup.getServiceItem(template[i]);
}
return item;
}
private static ServiceItem doLookup(ServiceTemplate template) {
return clientLookup.getServiceItem(template);
}
}
Introducing the ServiceUI Project
The ServiceUI project is a Jini community project that is standardizing the user interfaces to Jini
services. This is a broad topic because services have such a varied definition. A service can be
implemented on a small device, a PC, or a mainframe computer. However, the community
believes that it is important to have a standard definition to be able to "view" and therefore
interface with Jini services. Bill Venners, of www.artima.com is leading the ServiceUI project and
worked with the community to release a beta version of the ServiceUI API.
The ServiceUI
Basically, the ServiceUI defines three items used to associate a user interface with a Jini service:
The UI must supply
•
the user interface itself,
240
•
•
a user interface factory that produces the UI, and
a user interface descriptor that describes the UI.
A UI factory is an object that has one or more factory methods that produce and return a UI object.
A UI descriptor, an instance of net.jini.lookup.entry.UIDescriptor
(UIDescriptor), serves as a container for the UI factory and other objects that describe the
UI produced by the factory.
Because UIDescriptor implements net.jini.core.entry.Entry (Entry), a
UIDescriptor can be included in the attribute sets array of a Jini service item, thereby
associating the UI with the service. A UI descriptor contains four public fields:
•
•
•
•
Role—A String that gives the fully qualified name of a Java interface type that
represents the role played by the UI
Toolkit—A String that gives the name of the main package of the UI toolkit required
by the UI
Attributes—A java.util.Set of attribute objects that describe the UI produced by
the factory
Factory—A reference to a java.rmi.MarshalledObject that contains the UI
factory object in marshalled form
This code fragment creates a UIDescriptor entry that can be used to associate a user
interface with a Jini service. The example identifies a Swing JFrame factory and supplies the
object responsible for implementing the factory method: WorkPlaceFactory. The
WorkPlaceFactory is passed as a MarshalledObject.
// describe the UI in a Set
Set typeNames = new HashSet();
// signifies this factory produces a JFrame
typeNames.add(JFrameFactory.TYPE_NAME);
Set attribs = new HashSet();
attribs.add(new UIFactoryTypes(typeNames));
// create the factory object
MarshalledObject factory = null;
try {
// WorkPlaceFactory implements the factory method
factory = new MarshalledObject( new WorkPlaceFactory());
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
// Create the UIDescriptor entry
Entry[] entries =
new UIDescriptor(MainUI.ROLE,// Role string
WorkPlaceFactory.TOOLKIT,// Toolkit string
attribs,// attribute objects
factory)// UI factory
Eight factory interfaces are defined in the current version of the Jini service UI API.
•
DialogFactory— Returns an instance of class java.awt.Dialog, or one of
its subclasses, that depends on the AWT libraries but not the Swing libraries
241
•
•
•
•
•
•
•
FrameFactory— Returns an instance of class java.awt.Frame, or one of its
subclasses, that depends on the AWT libraries but not the Swing libraries
JComponentFactory— Returns an instance of class
javax.swing.JComponent, or one of its subclasses, that depends on both the
AWT and Swing libraries
JDialogFactory— Returns an instance of class javax.swing.JDialog, or
one of its subclasses, that depends on both the AWT and Swing libraries
JFrameFactory— Returns an instance of class javax.swing.JFrame, or one
of its subclasses, that depends on both the AWT and Swing libraries
JWindowFactory— Returns an instance of class javax.swing.JWindow, or
one of its subclasses, that depends on both the AWT and Swing libraries
PanelFactory— Returns an instance of class java.awt.Panel, or one of its
subclasses, that depends on the AWT libraries but not the Swing libraries
WindowFactory— Returns an instance of class java.awt.Window, or one of
its subclasses other than Frame or Dialog, that depends on the AWT libraries but not
the Swing libraries
From the Jini specification: "It is strongly encouraged that service and UI providers decouple the
class files required by a particular service item from the class files
Note
required by any UIs that may be associated with that service via UI descriptors in the service item.
If the class files for the service item and those of all the UIs associated with the service via UI
descriptors are placed into the same jar file, then a client will have to download all the class files
for all the UIs just to download the service item, even if the client never uses a single UI."
Basically, develop your deployment jar files to optimize expected service usage. If a service rarely
needs a graphical user interface, then bundle the UI class files in a separate jar file to conserve
resources.
Example ServiceUI Factory
The WorkPlaceFactory implements the JFrameFactory interface defined in the
net.jini.lookup.ui package. This is a new package and needs to be downloaded
separately from the Jini distribution . The ServiceUI download is freely available from the
www.jini.org site.
The JFrameFactory interface defines the single getJFrameMethod, which is passed
the ServiceItem associated with the user interface factory when you registered the service
with your attribute definition. This is a MarshalledObject downloaded when the service is
accessed:
Listing 10.12 The WorkPlaceFactory Class
package org.jworkplace.workplace;
import javax.swing.JFrame;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.entry.UIDescriptor;
import net.jini.lookup.ui.attribute.UIFactoryTypes;
import net.jini.lookup.ui.factory.JFrameFactory;
242
import net.jini.lookup.ui.MainUI;
public class WorkPlaceFactory implements JFrameFactory {
public JFrame getJFrame(Object obj) {
if(!(obj instanceof ServiceItem)) {
return null;
}
ServiceItem item = (ServiceItem)obj;
// Get the attribute set
Entry[] entries = item.attributeSets;
// loop through attributes looking for UIDescriptor
for(int i=0; i<entries.length; i++) {
if(entries[i] instanceof UIDescriptor) {
UIDescriptor descriptor = (UIDescriptor) entries[i];
// Ensure the role of this descriptor is for the
MainUI
if(descriptor.role.equals(net.jini.lookup.ui.MainUI.ROLE)) {
// Now create JFrame for WorkPlace, passing the
ServiceItem
JFrame frame = new WorkPlaceFrame(item, FRAME_TITLE);
return frame;
}
}
}
return null;
}
}
Of course, your factory can be as sophisticated as your application requires. The factory here
simply creates the WorkPlaceFrame, which is an extension of ServiceFrame that
implements the MainUI role (see Figure 10.7).
Figure 10.7. The WorkPlaceFactory creates the WorkPlaceFrame, which is
the main frame in the JWorkPlace application. Each user-defined service has an
associated UI controller that creates and controls interaction with JPanels, JLists,
JTables, and so on.
243
WorkPlaceFrame ensures that the ServiceItem implements the IndexManager
interface. At that point, you are probably in familiar territory in terms of user interface
development. All the common libraries, AWT, Swing, and so on, are available. What remains is
designing a user interface that cooperates with your service interface. Some sample code is
provided that demonstrates possible partitioning of responsibility across UI components.
Listing 10.13 The WorkPlaceFrame Class
package org.jworkplace.workplace;
import
import
import
import
net.jini.core.lookup.ServiceItem;
net.jini.lookup.ui.MainUI;
org.jworkplace.client.ServiceFrame;
org.jworkplace.content.IndexManager;
public class WorkPlaceFrame extends ServiceFrame {
public WorkPlaceFrame(ServiceItem item, String name) {
super(item, name);
if(item.service instanceof IndexManager) {
WorkPlaceController workPlace = new WorkPlaceController();
workPlace.initialize(item.service);
getContentPane().add(workPlace);
this.pack();
}
}
}
The ServiceController abstract class defines an initialize method that receives a
service object. For example, this is a reference to the proxy object that is returned from the Jini
ServiceRegistrar.
Listing 10.14 The ServiceController Class
import javax.swing.JComponent;
public abstract class ServiceController extends JComponent {
abstract public void initialize(Object service);
}
The AccountController is an example of a UI class that is initialized with the
AccountManager proxy object. It creates an AccountEditor that interfaces with a UI
model (AccountModel) of the account service.
Listing 10.15 The AccountController Class
import java.awt.BorderLayout;
import org.jworkplace.ui.ServiceController;
import org.jworkplace.client.WorkPlaceClient;
public class AccountController extends ServiceController {
private AccountEditor editor;
private AccountModel model;
private static String accountMgrClass =
AccountManager.class.getName();
244
public AccountController() {
super(); }
public void initialize(Object object) {
AccountManager accountMgr = null;
if(object instanceof AccountManager)
accountMgr = (AccountManager)object;
else
accountMgr = getServiceInterface();
model = new AccountModel(accountMgr);
editor = new AccountEditor(model);
setLayout(new BorderLayout());
add(editor, BorderLayout.CENTER);
}
// This method resolves the account interface if it was not
available
// or supplied at initialization
public AccountManager getServiceInterface() {
AccountManager manager = null;
try {
manager =
(AccountManager)WorkPlaceClient.getServiceInterface
(Class.forName(accountMgrClass));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return manager;
}
}
Running JWorkPlace
To run JWorkPlace, change to the Chapter 10 example 1 directory.
The startShare.cmd script can be found in the services/scripts directory. The
script is used to start an HTTP server for sharing your content in JWorkPlace. You must edit the
PORT and MYSHARE variables to match your installation. The script by default uses port 8081
and the directory chapter10\example1\MySpace for content. Any content that is
capable of being shared would be placed under this directory. Of course, you can edit this to any
location on your machine. The script invokes the Jini-supplied HTTP server. If you are using
another server, you must edit the command.
C:\JiniJavaSpaces\Chapter10\services\scripts>startShare
Listing 10.16 The startShare.cmd Script
@echo off
@call setup.cmd
set PORT=8081
set MYSHARE=chapter10\example1\MySpace
echo Jini and JavaSpaces Application Development
echo -------------------------------------------
245
echo
echo
echo
echo
echo
echo
Jini install directory
%JINI_HOME%
Examples install directory
%JWORK_HOME%
Default share port
%PORT%
Default share space
%JWORK_HOME%\%MYSHARE%
------------------------------------------Starting the Share Server...
java -jar %JINI_HOME%\lib\tools.jar -port 8081 -dir
%JWORK_HOME%\%MYSHARE% -trees -verbose
Now ensure that both the mahalo transaction service and the outrigger JavaSpaces service are
running. To run these services, check out the startMahalo.cmd and
startOutrigger.cmd scripts under the services/scripts directory.
Under the services/scripts directory is the startWorkPlace1.cmd script. Invoke
the command.
C:\JiniJavaSpaces\chapter10\services\scripts\startWorkPlace1.cmd
Under the chapter10/example1 directory is the startClient.cmd script. There are
two properties that must be set in the startClient.cmd script:
-Dorg.jworkplace.workport=%PORT%
-Dorg.jworkplace.workspace=%MYSHARE%
These properties need to match the PORT and MYSHARE variables set in the
startShare.cmd script. Invoke the command.
C:\JiniJavaSpaces\chapter10\example1\startClient.cmd
JWorkPlace is integrated into the Web. It allows you to communicate over a number of channels.
Figure 10.8 shows the JWorkPlace startup page.
Figure 10.8. The JWorkPlace sample application startup screen.
246
Note
Additional information on the code, scripts, and setup of JWorkPlace can be found at
www.jworkplace.org.
You must create an account using the AccountService (see Figure 10.9).
Figure 10.9. The JWorkPlace UI to the AccountService.
After creating an account, next you create a shared space. Map a local directory to a content space
name. The Add button allows you to add files to the Spaces area (see Figure 10.10).
247
Figure 10.10. You create a shared space from the CreateSpace panel.
To add a file to the MySpace area, simply select Add with the file selected (see Figure 10.11).
Figure 10.11. You add files to share from the Documents panel.
The left-hand panel displays the spaces that you have defined, and spaces that you have been
given access to. The right-hand panel displays all the users of JWorkPlace. You can map a space
to a user by selecting each entry in the table and pressing the share button (see Figure 10.12).
Figure 10.12. Sharing space in JWorkPlace from the Groups panel.
248
Summary
The peer-to-peer (P2P) revolution is affecting the development of applications. Decentralization
and dynamic community formation are powerful concepts being promoted.
Jini and JavaSpaces provide an excellent technology choice to meet the demand for decentralized
and ad hoc group formation. Jini offers an inherent capability to discover services and devices on
a network, and JavaSpaces easily supports network rendezvous points.
A rendezvous point provides a meeting place on the network. There you can provide messaging
capabilities, event notifications, and content space definitions you want to share with other group
members.
The ServiceUI project is a Jini community project that is standardizing the user interfaces to Jini
services. This is a broad topic, because services have such a varied definition. However, the
community believes that it is important to have a standard definition to be able to "view," and
therefore interface with Jini services.
JWorkPlace provides a sample GroupWare application using Jini and
JavaSpaces. Jini and JavaSpaces provide the necessary infrastructure
to build the next generation GroupWare applications.
249
Chapter 11. Workflow
IN THIS CHAPTER
•
•
•
•
•
•
•
•
What Is Workflow?
A Collaborative Workflow Framework Example
The Supporting Services
Change in State as a Flow-of-Objects
The Event Mailbox Service as a Remote Event Notification Platform
Integrating Workers with JavaSpaces and the Event Mailbox Service
Content Management Workflow
Summary
This chapter demonstrates the power of Jini and JavaSpaces in controlling distributed workflow
processes. It demonstrates the ease with which JavaSpaces can model processes as a flow-ofobjects. The chapter also presents my concept of STAR: (S)pace, (T)ime, (A)ctivity, and
(R)esource entries, which define the core model in an example collaborative workflow framework.
What Is Workflow?
In 1996, the Workflow Management Coalition published a glossary of terms related to workflow.
It defines workflow as
"The automation of a business process, in whole or part, during which documents, information or
tasks are passed from one participant to another for action, according to a set of procedural rules."
Workflow management is the automated coordination and control of work processes. A work
process is a collection of activities, typically stretching across and beyond organizational
boundaries. An activity can involve manual interaction with a user or workflow participant, or the
activity might be executed using computing resources.
Workflow plays a more strategic role in e-commerce because to be successful you must be able to
integrate business transactions into your workflow processes and back-end systems. Business-toconsumer (B2C) offerings have demonstrated the need for end-to-end service and fulfillment.
Failure to support products and processes across distribution channels diminishes your chances for
success.
However, integrating workflow and legacy systems has always been complex and costly.
JavaSpaces provides a simplified approach to dynamic communication, coordination, and sharing
of objects between network resources. JavaSpaces acts as a virtual space between providers and
requesters of network services. This allows participants in a distributed solution to exchange tasks,
requests, and information in the form of Java objects.
Jini, in concert with JavaSpaces, provides a mechanism to loosely coupled systems through
remote events and a persistent shared memory model. Processes can exchange information
through a common space and be notified of changes through asynchronous events. The simplicity
of the JavaSpaces API provides an easy-to-learn interface, yet it still provides a powerful
programming model.
As we move to component and service-based architectures, coordination and automation of
processes both internal and external to an organization will take on more importance. Efficiencies
250
are gained by integrating internal systems with internal users, partner systems, and external
customers. Supply chains will become increasingly dependent on technology. Coordination of
activities and dissemination of information across organizational boundaries are key factors to
improving chain effectiveness. This is the first step in creating the virtual organization, where
partner systems, employees, and customers collaborate to produce and provide goods and services
(see Figure 11.1).
Figure 11.1. There are three levels of collaborative development required in the
virtual organization.
Generic workflow engines supporting standard APIs will help integrate both internal and external
processes. Jini and JavaSpaces should also be considered as a network-centric middleware
solution for enabling next generation communication and collaboration.
This chapter demonstrates how JavaSpaces can be used to control workflow among collaborating
participants.
What Is a Workflow Management System?
The Workflow Management Coalition (http://www.wfmc.org/standards/docs/TC1011_term_glossary_v3.pdf) defines a workflow management system as
"A system that defines, creates and manages the execution of workflows through the use of
software, running on one or more workflow engines, which is able to interpret the process
definition, interact with workflow participants and, where required, invoke the use of IT tools and
applications."
A Workflow Management System can interpret a workflow definition, manage triggers and alarms,
and interoperate with external systems. Workflow management benefits include reduced operating
costs, improved productivity, faster processing times, and improved communication.
251
Workflow System Frameworks
Workflow management systems support different models of execution for different types of
control and automation. You can classify workflow frameworks according to their deployment
characteristics and execution goals.
Production Workflow
The key goal of production workflow is to manage many similar tasks and to optimize
productivity. This is achieved by automating as many activities as is practical. Human input is
required only to manage exceptions. Production workflow can be tightly integrated with existing
(legacy) systems. Performance is usually of paramount importance.
Autonomous Workflow Engines
An autonomous workflow management system is functional without any additional application
software. Autonomous workflow management systems are separate pieces of application software
that provide the workflow functionality. They are usually installed to support a variety of different
applications. The modeling of autonomous workflow applications requires the specification of
interface information for the invoked applications, relevant data structures, and involved
participants, and thus can become a complex challenge.
Embedded Workflow
An embedded workflow management system is only functional if it is employed with the
surrounding (embedding) system—for example, an Enterprise Resource Planning (ERP) system.
The workflow components are used to control the sequence of the application's functions, to
manage queues, and to assist with exception processing.
Collaborative Workflow
Collaborative workflow focuses on teams working together toward common goals. Groups can
vary from small, project-oriented teams, to widely dispersed people with interests in common.
Ad Hoc Workflow
Ad hoc workflow systems allow users to create and amend Process Definitions quickly and easily
to meet circumstances as they arise. It is possible to have almost as many Process Definitions as
there are instances of the definitions. Ad hoc workflow maximizes flexibility in areas where
throughput and security are not major concerns. Whereas in production workflow the organization
owns the process, ad hoc workflow users own their own processes.
Workflow Reference Model
The Workflow Reference Model defined by the Workflow Management Coalition defines five
interoperable interfaces (see Figure 11.2):
•
•
Process Definition—The computerized representation of a process that includes the
manual definition and workflow definition. This interface deals with passing Process
Definitions from external tools to the workflow engine where they are enacted.
Workflow Client and Invoked Application—These interfaces have been combined and
cover the WAPIs (Workflow APIs). The support of these interfaces in workflow
management products allows the implementation of front-end applications that need to
access workflow management engine functions (workflow services). Such
implementations might be written by workflow management exploiters or workflow
252
•
•
systems integrators. Integration between workflow and other desktop tasks (calendar,
mail, reminders, etc.) is often a common target, and the workflow APIs allow workflow
task integration into a common desktop.
Workflow Enactment Service—A software service that may consist of one or more
workflow engines in order to create, manage, and execute workflow instances.
Applications may interface to this service via the workflow application programming
interface (WAPI). This interface defines the mechanisms that workflow product vendors
are required to implement in order that one workflow engine may make requests of
another workflow engine to effect the selection, instantiation, and enactment of known
process definitions by that other engine. The requesting workflow engine is also able to
pass context data (workflow-relevant or application data) and receive back status
information and the results of the enactment of the process definition.
Audit and Monitoring—The support of this specification in workflow products allows
analysis of consistent audit data across heterogeneous workflow products. During the
initialization and execution of a process instance, multiple events occur that are of interest
to a business, including WAPI events, internal workflow management engine operations
and other system and application functions. With this information, a business can
determine what has occurred in the business operations managed by workflow and take
the appropriate actions.
Figure 11.2. The Workflow Reference Model.
A Collaborative Workflow Framework Example
Now that the importance of workflow and the basic components and types of workflow
frameworks have been highlighted, you can put this knowledge to use by building a workflow
service using Jini and JavaSpaces.
Collaborative workflow focuses on teams working together toward common goals. Groups can
vary from small, project-oriented teams, to widely dispersed people with common interests.
Effective use of collaborative workflow is now considered a vital element in the success of
253
enterprises of all kinds. Throughput is not as important as in production workflow systems, and
process definitions are not rigid and can be amended frequently.
This example will dovetail off the collaborative capabilities introduced in the last chapter. You
will build a system capable of the following:
•
•
•
Defining process definitions and work items
Scheduling tasks and assigning resources
Notifying participants when dependent tasks have completed and new tasks can start
As you should recall from Chapter 5, "JavaSpaces Service," JavaSpaces is well suited for systems
that can be modeled as a flow-of-objects, which is typical of workflow systems.
This example focuses on three aspects of the workflow application:
•
•
•
Modeling change in activity state as a flow-of-objects
Using the Event Mailbox Service as a remote event notification platform
Integrating process workers with JavaSpaces and the Event Mailbox Service
Figure 11.3 depicts the flow of tasks through the workflow system.
Figure 11.3. Collaborative Workflow Framework using Jini, JavaSpaces, and the
Event Mailbox Service.
An administrator or process owner is responsible for defining workflow templates. These
templates define work items that are assigned to members of the group. Descriptions of the task
and start and stop time parameters are included. After the process has been modeled, the definition
is exported to the remote Workflow Service.
254
The Workflow Service provides a process definition interface and a work process interface that
represent the Process Definition and the Workflow Client Application interfaces in the Workflow
Reference Model.
The Workflow Service engine is responsible for activating the workflow process and updating the
activities as workers update associated tasks. Task assignments and task status are received by
workers asynchronously, and the overall progress of the process can be monitored.
The Supporting Services
A number of Jini-specific services are required in a collaborative workflow.
Reggie, Outrigger, Mahalo, Mercury
In addition to the lookup service, your workflow application will require JavaSpaces and the
transaction service (see Figure 11.4). JavaSpaces provides a network-sharable space for storing
activity definitions and state change notifications. Again, the transaction service is required by
JavaSpaces. In addition, you will use the Event Mailbox Service (mercury), to store event
notifications for workers (resources) defined in your workflow application.
Figure 11.4. Services comprising your workflow application.
Three new services are also defined:
•
•
•
The WorkflowService implements the workflow enactment and workflow engine
process.
The StarService provides a proxy to the JavaSpace API with additional workflow
semantics.
The MailboxManager provides a user mapping manager to the EventMailbox
service.
Now let's look at the entries and definitions specific to your workflow service.
Change in State as a Flow-of-Objects
In this section, the entries comprising your collaborative workflow are defined and described.
The Tuples Defining Workflow Space
255
The StarEntry is the basic tuple defined in JavaSpaces that encapsulates your definition of a
process activity and its supporting environment:
•
•
•
•
Space refers to a location-dependent place that the activity requires knowledge of. In the
example Workflow Service, the process ID is used as a unique reference to a location in
JavaSpaces.
Time is used to schedule the start and completion time of an activity. It can be extended
to include other time-based measurements to track performance, determine lease
durations, and schedule alert notifications.
Activity is the basic definition of a step in the process and encapsulates the activity
description and expected output or result of the process step.
Resource abstracts the assignment of an individual, a group, or another system to the
activity.
Figure 11.5. A STAR is used to encapsulate the basic elements of workflow.
StarEntries are written to JavaSpaces using the StarInterface (see Listing 11.3).
StarEntries can be linked to form a constellation that in effect provides a graphical view of
the relationship and dependencies between tasks. This is akin to the Gant chart often used in
project management tools.
Modeling State as Objects
The ActivityState interface defines the interface all state objects implement in your
workflow system. State objects implement a common interface that changes behavior dependent
on the current status of the activity. For instance, the start method invoked on a "ready" task is
well defined; however, the start method on a "complete" task throws an invalid state change
exception:
Listing 11.1 The ActivityState Interface
package org.jworkplace.workflow;
import java.rmi.RemoteException;
import java.io.Serializable;
public interface ActivityState
{
public static final String
public static final String
public static final String
public static final String
public static final String
extends Serializable
TASK_INIT = "inactive";
TASK_READY = "ready";
TASK_START = "start";
TASK_COMPLETE = "complete";
TASK_SUSPEND = "suspend";
public void start() throws RemoteException;
256
public void terminate() throws RemoteException;
public void suspend() throws RemoteException;
public void resume() throws RemoteException;
}
Controlling and Managing Process Flow
The basic process control states for activities are as follows:
•
•
•
•
•
Inactive is the initial state of an activity. The activity has been defined but has not been
started.
Start/Running is the state of an activity once acknowledged by a worker. This indicates
that the underlying activity is in progress and tracking against the current schedule
parameters.
Complete means the activity has fulfilled the conditions for completion, and any internal
operations such as logging audit data or statistics will be performed. Complete signifies
that dependent successor tasks can be moved to a "ready" state.
Suspend is the state of an activity that has been placed on hold, and no activities are
started until the process has returned to the running state (via a resume command).
Ready is the state of an activity that has all dependent tasks complete but has not been
acknowledged or started by a user or system (via a start command).
The system defines an associated state object entry that extends TaskEntry for each state
defined:
Listing 11.2 The TaskEntry Class
package org.jworkplace.workflow;
import java.rmi.RemoteException;
import net.jini.entry.AbstractEntry;
import com.sun.jini.proxy.UUID;
public class TaskEntry extends AbstractEntry implements ActivityState
{
public UUID processId;
public String taskId;
public TaskEntry() {
this(null); }
public TaskEntry(UUID processId) {
this(null, null); }
public TaskEntry(UUID processId, String taskId) {
this.processId = processId;
this.taskId = taskId;
}
// Sub-classes implement the desired behavior
// for each command dependent on current state
public void start() throws RemoteException { }
public void terminate() throws RemoteException { }
public void suspend() throws RemoteException { }
public void resume() throws RemoteException { }
}
The state classes that extend TaskEntry are InactiveTask, ActiveTask,
ReadyTask, CompleteTask, and SuspendTask.
257
Figure 11.6. The TaskEntry abstract class provides the base class for state
objects in your Workflow Service.
As workers acknowledge, accept, and complete activities, corresponding task objects are written
to JavaSpaces.
The StarService
The StarInterface provides methods that map to the JavaSpace API. The
StarInterface delegates requests to the JavaSpace proxy and provides some additional
implied semantics for workflow processing. The StarService implements the
StarInterface and is responsible for resolving the reference to the JavaSpaces service.
Listing 11.3 The StarInterface Interface
package org.jworkplace.workflow;
import
import
import
import
import
import
import
public
java.rmi.RemoteException;
java.rmi.MarshalledObject;
java.io.IOException;
net.jini.core.entry.Entry;
net.jini.core.transaction.TransactionException;
net.jini.core.entry.UnusableEntryException;
net.jini.core.event.RemoteEventListener;
interface StarInterface extends java.io.Serializable {
public void setRemoteEventListener(Entry template,
MarshalledObject handback,
RemoteEventListener listener);
public Entry read(Entry template)
public Entry readIfExists(Entry template)
public void write(Entry entry)
public void write(Entry entry, long leaseTime)
public Entry take(Entry entry)
public Entry takeIfExists(Entry entry)
}
The setRemoteEventListener method takes an Entry template of a
MarshalledObject and a RemoteEventListener object as parameters and invokes
the JavaSpace notify method to register for remote event notification. This provides a
simple mechanism to track the writing of state objects to space.
public synchronized void setRemoteEventListener(Entry entry,
MarshalledObject handback,
RemoteEventListener listener)
{
space.notify(entry, null, listener, Lease.FOREVER, handback);
258
}
The MarshalledObject contains a reference to a process definition. The listener contains a
reference to the EventMailbox listener (see Listing 11.5). The Entry template is the
CompleteTask object initialized with a specific instance of a process to monitor.
A StarEntry is written for each activity defined in a workflow process by calling the write
method of the StarInterface. The StarService implementation writes the
StarEntry and an associated TaskEntry that parallels the current state of the activity:
public void write(Entry entry)
throws RemoteException,
TransactionException,
UnusableEntryException,
InterruptedException
{
if(entry instanceof StarEntry) {
StarEntry template = (StarEntry)entry;
// get the location, taskId, and status from the StarEntry
UUID location = template.location;
String taskId = template.taskId;
String command = template.status;
// using the StarEntry processID and taskID
// remove the current TaskEntry (state object) for
// work process activity
TaskEntry taskTemplate = new TaskEntry(location, taskId);
takeIfExists(taskTemplate);
// now create and update the current TaskEntry to reflect
// the StarEntry status
// initializing - create an inactive task
if(command.equals(ActivityState.TASK_INIT)) {
InactiveTask task = new InactiveTask(location, taskId);
space.write(task, null, Long.MAX_VALUE);
// starting - move task to the start state
} else if (command.equals(ActivityState.TASK_START)) {
ActiveTask task = new ActiveTask(location, taskId);
task.start();
space.write(task, null, Long.MAX_VALUE);
// ready for activation - move task to the ready state
} else if (command.equals(ActivityState.TASK_READY)) {
ReadyTask task = new ReadyTask(location, taskId);
space.write(task, null, Long.MAX_VALUE);
// completing - move task to the complete state
} else if (command.equals(ActivityState.TASK_COMPLETE)) {
CompleteTask task = new CompleteTask(location, taskId);
task.terminate();
space.write(task, null, Long.MAX_VALUE);
// suspending - move task to the suspended state
} else if (command.equals(ActivityState.TASK_SUSPEND)) {
SuspendTask task = new SuspendTask(location, taskId);
task.suspend();
space.write(task, null, Long.MAX_VALUE);
259
}
// remove the current StarEntry
StarEntry removalTemplate = new StarEntry();
removalTemplate.location = location;
removalTemplate.taskId = taskId;
takeIfExists(removalTemplate);
// write the new StarEntry
Lease lease = space.write(template, null, Lease.FOREVER);
}
}
The StarEntry reflects the current status of a task in a work process and contains the STAR
information. The TaskEntry models the state changes that occur on a task. The write
method removes the current TaskEntry if it exists and creates a new entry based on the
StarEntry status. The status of the StarEntry in effect is the state change command to the
created TaskEntry. Illegal state changes are thrown in the TaskEntry subclasses. The
current StarEntry is removed and a new StarEntry is written to reflect the updated task
status.
These state objects trigger event notifications to listeners that have registered for state change
events. You use this mechanism to notify the EventMailbox listeners of a change in activity
state, which is explained in the next section.
The Event Mailbox Service as a Remote Event Notification
Platform
This section describes the Event Mailbox service and defines a MailboxManager for your
collaborative workflow application.
The Event Mailbox Service (Mercury)
Chapter 7, "The Helper Services," explained how the Event Mailbox service can be used by a
client to store event notifications on its behalf. When an entity registers with the Event Mailbox
Service, that service will collect events intended for the registered entity until the entity initiates
delivery of the events.
The Event Mailbox service is defined by the EventMailbox interface:
Listing 11.4 The EventMailbox Interface
public interface EventMailbox
{
MailboxRegistration register (long leaseDuration) throws
RemoteException;
}
Clients invoke the register method on the EventMailbox and receive a
MailboxRegistration as a result:
260
Listing 11.5 The MailboxRegistration Interface
package net.jini.event;
public interface MailboxRegistration
{
Lease getLease();
RemoteEventListener getListener();
void enableDelivery(RemoteEventListener target)
throws RemoteException;
void disableDelivery() throws RemoteException;
}
With the MailboxRegistration interface, an entity can manage the time and location of
remote event delivery. A using entity retrieves a RemoteEventListener from the
registration object and uses this listener in any method that requires a
RemoteEventListener. The using entity can then disconnect from the network and not
worry about losing events. The events will be directed and stored by the mailbox listener. Later,
the using entity can activate and invoke the enableDelivery method specifying a
RemoteEventListener that becomes the target for the saved events. The using entity can
toggle between enabling and disabling delivery until the entity terminates or allows its lease to
expire with the mailbox service.
The JavaSpace interface method of interest is the notify method. It takes a
RemoteEventListener as a parameter:
EventRegistration notify(Entry tmpl, Transaction txn,
RemoteEventListener listener, long lease,
MarshalledObject handback)
throws RemoteException, TransactionException;
A notify request registers interest in future incoming entries to the JavaSpaces service that
matches the specified template.
When matching entries are written, the specified RemoteEventListener will be notified.
When you invoke notify, you provide an upper boundary on the lease time, which is how long
you want the registration to be remembered by the JavaSpaces service.
Each notify returns a net.jini.core.event.EventRegistration object.
When an object is written that matches the template supplied in the notify invocation, the
listener's notify method is invoked with a RemoteEvent object.
Linking the Event Mailbox service with JavaSpaces is as easy as the following code fragment:
EventMailbox mailbox;
JavaSpace space;
// register with the EventMailbox Service
MailboxRegistration reg = mailbox.register (Lease.FOREVER);
// get the remote event listener for the registration
RemoteEventListener listener = reg.getListener();
// create a template to match on
SomeEntry template = new SomeEntry();
// create a MarshalledObject to handback
261
MarshalledObject handback = new MarshalledObject(myData);
// supply the template, listener, and handback to the JavaSpace
notify method
EventRegistration eventReg = space.notify(template,
null,
listener,
Lease.FOREVER,
handback)
When an entry matching the template is written to JavaSpace, the listener (mailbox) is notified
with a RemoteEvent object. The delivery of the notifications is now under your control
through the enableDelivery method of the MailboxRegistration:
// now enable delivery to the supplied listener
reg.enableDelivery(myRemoteListener);
This method allows you to deactivate (log out) from the workflow system and not miss events that
might occur during your absence. When you log back in, you can enable delivery and get all the
events targeted for your mailbox registration (see Figure 11.7).
Figure 11.7. Using the Event Mailbox service to queue JavaSpace notifications.
The MailboxService
The MailboxService provides the management of the Event Mailbox registrations and
event delivery to the users of the workflow system.
The MailboxManager Interface
262
The MailboxManager interface is a wrapper to the EventMailbox and
MailboxRegistration interfaces. It allows you to map users to registrations as seen in
Listing 11.6.
Listing 11.6 The MailboxManager Class
package org.jworkplace.mailbox;
import
import
import
import
import
java.rmi.RemoteException;
net.jini.core.lease.LeaseDeniedException;
net.jini.core.lease.UnknownLeaseException;
net.jini.core.event.RemoteEventListener;
net.jini.admin.Administrable;
public interface MailboxManager extends Administrable {
public void register(String user, long duration) throws
RemoteException,
LeaseDeniedException;
public RemoteEventListener getListener(String user) throws
RemoteException;
public void start(String user, RemoteEventListener listener)
throws RemoteException;
public void pause(String user) throws RemoteException;
public void stop(String user) throws RemoteException,
UnknownLeaseException;
}
The MailboxService implements the RemoteMailboxManager interface. Again you
will extend your ServiceImpl class, which should be familiar to you by now:
Listing 11.7 The MailboxService Class
package org.jworkplace.mailbox;
import
import
import
import
import
import
java.rmi.Remote;
java.rmi.RemoteException;
java.rmi.RMISecurityManager;
java.rmi.server.RemoteObject;
java.io.File;
java.io.IOException;
import
import
import
import
import
import
java.util.Set;
java.util.Map;
java.util.HashSet;
java.util.HashMap;
java.util.Collection;
java.util.Collections;
import
import
import
import
import
import
import
net.jini.discovery.LookupDiscovery;
net.jini.discovery.DiscoveryListener;
net.jini.discovery.DiscoveryEvent;
net.jini.core.lookup.ServiceRegistrar;
net.jini.core.entry.Entry;
net.jini.lookup.entry.Name;
net.jini.lookup.entry.ServiceInfo;
import net.jini.event.EventMailbox;
263
import
import
import
import
import
import
import
net.jini.event.MailboxRegistration;
net.jini.lease.LeaseRenewalService;
net.jini.lease.LeaseRenewalManager;
net.jini.core.lease.Lease;
net.jini.core.lease.LeaseDeniedException;
net.jini.core.lease.UnknownLeaseException;
net.jini.core.event.RemoteEventListener;
import org.jworkplace.util.ServiceFinder;
import org.jworkplace.service.*;
public class MailboxService extends ServiceImpl implements
ServiceAdmin,
RemoteMailboxManager,
DiscoveryListener
{
// The interface to the EventMailbox
private EventMailbox mailbox;
// The lease renewal manager to renew the registration leases
// this could also use the LeaseRenewalService
private LeaseRenewalManager leaseRenewalMgr;
// performs Lookup discovery of the EventMailbox service
private LookupDiscovery mgt;
// A mapping of user to registration
private Map userMap;
public static void main(String args[]) throws Exception {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
new MailboxService(args);
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
try {
Thread.sleep(Long.MAX_VALUE);
} catch(InterruptedException e) {
}
System.exit(0);
}
//
public MailboxService(String[] args) throws IOException {
super(args);
// Enable a thread safe collection
userMap = Collections.synchronizedMap(new HashMap());
// Instantiate the lease renewal manager
leaseRenewalMgr = new LeaseRenewalManager();
// now start finding registrars (lookup services)
mgt = new LookupDiscovery(LookupDiscovery.NO_GROUPS);
mgt.addDiscoveryListener(this);
((LookupDiscovery)mgt).setGroups(LookupDiscovery.ALL_GROUPS);
264
}
// register the supplied user with the EventMailbox service
// and store the mapping
public void register(String user, long duration) throws
RemoteException, LeaseDeniedException
{
MailboxRegistration reg = mailbox.register(duration);
leaseRenewalMgr.renewFor(reg.getLease(), duration, null);
userMap.put(user, reg);
}
// get the listener for this user
public RemoteEventListener getListener(String user) throws
RemoteException {
MailboxRegistration reg =
(MailboxRegistration)userMap.get(user);
if(reg != null) {
return reg.getListener();
}
return null;
}
// enable delivery to the listener specified
public void start(String user, RemoteEventListener listener)
throws
RemoteException {
MailboxRegistration reg =
(MailboxRegistration)userMap.get(user);
if(reg != null)
reg.enableDelivery(listener);
}
// disable delivery and queue notifications
public void pause(String user) throws RemoteException {
MailboxRegistration reg =
(MailboxRegistration)userMap.get(user);
if(reg != null)
reg.disableDelivery();
}
// disable and stop notification
public void stop(String user) throws RemoteException,
UnknownLeaseException {
// cancel lease
MailboxRegistration reg =
(MailboxRegistration)userMap.get(user);
if(reg != null) {
Lease lease = reg.getLease();
lease.cancel();
}
}
/*
** DiscoveryListener Interface
*/
public synchronized void discarded(DiscoveryEvent e) { }
// Find the EventMailbox Service
public synchronized void discovered(DiscoveryEvent de) {
// get the array of lookup services discovered
ServiceRegistrar[] registrars = de.getRegistrars();
265
// If we already have found the EventMailbox return
if(mailbox == null) {
try {
// loop through the LUS's to find the EventMailbox
for(int i=0; i < registrars.length; i++) {
// Call the ServiceFinder utility
mailbox =
(EventMailbox)ServiceFinder.findEMS(registrars[i]);
if(mailbox != null) {
// Terminate the discovery threads
mgt.terminate();
break;
}
}
} catch (Exception e) { e.printStackTrace(); }
}
}
}
The MailboxService provides a user mailbox mapping capability. As users become
workers—for example, assigned to a task—the WorkflowService registers the user with the
Event Mailbox service. The WorkflowService then creates a StarEntry template using
the worker as the Resource and registers for matching entry space notifications. Workers are
notified when any StarEntry assigned to them is written to space.
Integrating Workers with JavaSpaces and the Event
Mailbox Service
In this section you integrate the services and users of your collaborative workflow application.
The Workflow Service
The WorkflowManager interface provides the interface to administering process definitions
in the Workflow Service. Note that it extends Administrable, so it defines the getAdmin
method:
Listing 11.8 The WorkflowManager Interface
package org.jworkplace.workflow;
import java.rmi.RemoteException;
import net.jini.admin.Administrable;
import com.sun.jini.proxy.UUID;
import org.jworkplace.mailbox.MailboxManager;
/*
** This interface defines the methods required to manage
** workflow process definitions
*/
public interface WorkflowManager extends Administrable {
// create a workflow process, the UUID is a universal unique
266
// identifier generated by the workflow system
public UUID create(ProcessDef processDef) throws RemoteException;
// retrieve a process definition
public ProcessDef getProcessDef(String owner, String processName)
throws RemoteException;
// get all process names
public String[] getProcessNames(String owner) throws
RemoteException;
// update an existing process
public void updateProcess(UUID processId, ProcessDef processDef)
throws RemoteException;
// remove an existing process definition
public void removeProcess(UUID processId) throws RemoteException;
// get the mailbox manager proxy interface
public MailboxManager getMailboxManager() throws RemoteException;
// create a process listener for a type of task
public void createProcessListener(UUID processId, TaskEntry
taskEntry)
throws RemoteException;
}
The methods defined in this interface allow the importing and exporting of process definitions to
using entities. There is also a getMailboxManager method that returns a proxy to the
MailboxManager associated with the WorkflowService and a
createProcessListener that can be used to register interest in a type of event. The
workflow engine registers interest in CompleteTask notifications for each process defined.
Workflow Process Definition
Your workflow process definition is composed of a collection of work items (see Figure 11.8).
Work items model specific tasks in the workflow system. Of course, workflow applications can
use any number of approaches. For example, you could use XML to define process templates and
task execution policy. To keep the example simple, you will create a direct mapping between
work item and task definition.
Figure 11.8. A ProcessDef object contains a time-sorted list of work items.
Process Definition
267
The process definition is contained in a ProcessDef class. A unique ID is associated with
every process defined in the workflow system. The unique ID is contained in the class
com.sun.jini.proxy.UUID and generated by the factory
com.sun.jini.proxy.UUIDFactory:
Listing 11.9 The ProcessDef Class
public class ProcessDef extends AbstractEntry
{
// unique id assigned by Workflow service
private UUID id;
// The human readable name of the process template
private String processName;
// The owner of the process definition
private String processOwner;
// A time sorted work activity list
private Map workItemList;
public ProcessDef() {
this(null, null);
}
public ProcessDef(String processName, String processOwner) {
this.processName = processName;
this.processOwner = processOwner;
workItemList = Collections.synchronizedSortedMap(new
TreeMap());
}
public
public
public
public
public
owner; }
void setId(UUID id) { this.id = id; }
UUID getId() { return id; }
String getProcessName() { return processName; }
String getProcessOwner() { return processOwner; }
void setProcessOwner(String owner) { this.processOwner =
public void addWorkItem(WorkItem item) throws RemoteException
{
workItemList.put(item, item);
}
public void updateWorkItem(WorkItem item) throws RemoteException
{
removeWorkItem(item);
addWorkItem(item);
}
public WorkItem getWorkItem(String id) throws RemoteException {
System.out.println("getWorkItem: " + id);
Iterator itr = (workItemList.values()).iterator();
while(itr.hasNext()) {
WorkItem item = (WorkItem)itr.next();
if( item.getId().equals(id)) {
return item;
}
}
return null;
}
268
public WorkItem getWorkItem(WorkItem template) throws
RemoteException
{
return getWorkItem(template.getId());
}
public WorkItem getFirstItem() throws RemoteException {
WorkItem[] items = getWorkItems();
return items[0];
}
public WorkItem getNextItem(String id) throws RemoteException
{
WorkItem successor = null;
WorkItem item = getWorkItem(id);
if(item != null) {
SortedMap tailView =
((SortedMap)workItemList).tailMap(item);
Object[] array = (tailView.keySet()).toArray();
if(array.length > 1) {
successor = (WorkItem)array[1];
}
}
return successor;
}
public void removeWorkItem(WorkItem template) throws
RemoteException
{
Iterator itr = (workItemList.values()).iterator();
while(itr.hasNext()) {
WorkItem item = (WorkItem)itr.next();
if( item.getId().equals(template.getId())) {
workItemList.remove(item);
break;
}
}
}
public WorkItem[] getWorkItems() throws RemoteException
{
Collection collection = workItemList.values();
return (WorkItem[])collection.toArray(new WorkItem[0]);
}
}
Work items are defined and assigned to specific workers by the administrator. In a more robust
application, assignment may be to a specific role and then to a specific individual. This gives you
the capability to separate process definition from process execution. The Workflow Service
example defines a Resource class that encapsulates assignments. The Time class contains a
scheduled start and stop time for the activity. The WorkItem implements Comparable to
maintain a time sequence ordering to the item collection.
Listing 11.10 The WorkItem Class
package org.jworkplace.workflow;
public class WorkItem implements Comparable, java.io.Serializable {
// human readable identifier
public String itemName;
269
// description of the work item
public String description;
// Start and stop time
public Time time;
// worker assigned
public Resource assignedTo;
public WorkItem() { this(null, null, null, null); }
public WorkItem(String itemName,
String description,
Time time,
Resource resource)
{
this.itemName = itemName;
this.description = description;
this.time = time;
this.assignedTo = resource;
}
//
// code omitted
//
// Used to sort workitems by start time
public int compareTo(Object obj) {
WorkItem item = (WorkItem)obj;
if (this == item)
return 0;
long thisStart = this.getStartTime().longValue();
long itemStart = item.getStartTime().longValue();
if ( thisStart < itemStart ||
thisStart == itemStart )
return -1;
return 1;
}
}
The WorkflowService Implementation
The WorkflowService provides the primary administration and process activation for your
workflow system. It implements the remote version of the WorkflowManager and
WorkProcess interfaces defined in the WorkflowBackend. It is also responsible for
resolving access to the MailboxManager and the StarService using a
DiscoveryListener.
There are three primary mappings performed by the WorkflowService:
•
•
•
The processMap provides a process ID to process definition mapping.
The userMap provides a worker to process mapping.
The ownerMap provides a process administrator to process mapping.
Listing 11.11 The WorkflowService Class
package org.jworkplace.workflow;
import org.jworkplace.workplace.WorkPlaceFactory;
import org.jworkplace.login.LoginHandler;
270
import
import
import
import
org.jworkplace.mailbox.MailboxManager;
org.jworkplace.account.Session;
org.jworkplace.util.ServiceFinder;
org.jworkplace.service.*;
public class WorkflowService extends ServiceImpl implements
ServiceAdmin,
WorkflowBackend, DiscoveryListener
{
/** UUID generator */
private UUIDFactory uuidFactory = new UUIDFactory();
// processId to processDef
private Map processMap;
// worker to processId
private Map userMap;
// owner to list of owned processDefs
private Map ownerMap;
// thread to invoke process instance
private WorkflowEngine engine;
// JavaSpace proxy interface
private StarInterface space;
private static String starInterfaceClass =
StarInterface.class.getName();
// Event Mailbox proxy interface
private MailboxManager mailbox;
private static String mailboxManagerClass =
MailboxManager.class.getName();
// process definition queue
private UUID processToActivate;
private boolean ready = false;
// Lookup discovery manager
private LookupDiscovery mgt;
public static void main(String args[]) throws Exception {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
new WorkflowService(args);
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
try {
Thread.sleep(Long.MAX_VALUE);
} catch(InterruptedException e) {
System.exit(0);
}
271
}
public WorkflowService(String[] args) throws IOException {
super(args);
// processId to processDef
processMap = Collections.synchronizedMap(new HashMap());
// worker to processId
userMap = Collections.synchronizedMap(new HashMap());
// owner to list of owned processDefs
ownerMap = Collections.synchronizedMap(new HashMap());
// initialize factory
UUID uuid = uuidFactory.newUUID();
// now start finding registrars (lookup services)
mgt = new LookupDiscovery(LookupDiscovery.NO_GROUPS);
mgt.addDiscoveryListener(this);
((LookupDiscovery)mgt).setGroups(LookupDiscovery.ALL_GROUPS);
// start threads
engine = new WorkflowEngine();
}
//
// Workflow Manager Interface
//
// create the process definition and queue the process for the
engine thread
public UUID create(ProcessDef processDef)throws RemoteException
{
// generate a unique id for this process
UUID uuid = uuidFactory.newUUID();
processDef.setId(uuid);
processMap.put(uuid, processDef);
// get the owner and determine if other processes
// belonging to this owner are already defined
String owner = processDef.getProcessOwner();
if(!ownerMap.containsKey(owner)) {
List processList = new ArrayList();
ownerMap.put(owner, processList);
}
// add this process definition to the owners list
List processList = (List)ownerMap.get(owner);
int index = -1;
index = ( (processList.lastIndexOf(owner) == -1 )
? processList.size() :
index+1);
processList.add(index, processDef);
// notify the engine a process instance should be created
queueProcess(uuid);
// return the unique reference
return uuid;
}
// returns a Process Defintion given a owner and process name
public ProcessDef getProcessDef(String owner, String processName)
272
throws RemoteException
{
List processList = (List)ownerMap.get(owner);
if(processList == null)
throw new RemoteException();
ProcessDef item = null;
Iterator itr = processList.iterator();
while(itr.hasNext()) {
item = (ProcessDef)itr.next();
if(processName.equals( item.getProcessName()))
break;
}
return item;
}
// returns the list of processes defined by a given owner
public String[] getProcessNames(String owner) throws
RemoteException
{
List processList = (List)ownerMap.get(owner);
if(processList == null)
throw new RemoteException();
Iterator itr = processList.iterator();
ArrayList nameList = new ArrayList();
while(itr.hasNext()) {
ProcessDef item = (ProcessDef)itr.next();
nameList.add(item.getProcessName());
}
return (String[])nameList.toArray(new String[0]);
}
// updates a process defintion
public void updateProcess(UUID processId, ProcessDef processDef)
throws RemoteException {
processMap.put(processId, processDef);
}
// removes a process definition
public void removeProcess(UUID processId) throws RemoteException
{
processMap.remove(processId);
}
// return the MailboxManager used by this workflow service
public MailboxManager getMailboxManager() throws RemoteException
{
return mailbox;
}
// Called by the engine thread with a CompleteTask object
// during process instantiation
public void createProcessListener(UUID processId, TaskEntry
taskEntry)
throws
RemoteException
{
// get the process definition associated with this id
ProcessDef processDef = (ProcessDef)processMap.get(processId);
273
try {
// create a TaskListener
TaskListener listener = new TaskListener(
(WorkflowBackend)WorkflowService.this,
space);
// pass the TaskListener the Process Definition
MarshalledObject handback = new
MarshalledObject(processDef);
// register for notification of state objects
space.setRemoteEventListener(taskEntry, handback,
listener);
}
}
catch(Exception e) {
e.printStackTrace(); }
// waits for next process definition
public synchronized UUID getProcessId()
{
while (ready == false) {
try {
wait();
} catch (InterruptedException e) { }
}
ready = false;
notifyAll();
return processToActivate;
}
// queues a process to activate the process engine
public synchronized void queueProcess(UUID value)
{
while (ready == true) {
try {
wait();
} catch (InterruptedException e) { }
}
processToActivate = value;
ready = true;
notifyAll();
}
The WorkProcess Interface
The WorkProcess interface provides the Client Application API to the
WorkflowService. It allows the client to connect and disconnect to the
WorkflowService and to receive task assignments and update tasks assigned:
Listing 11.12 The WorkProcess Interface
package org.jworkplace.workflow;
import java.rmi.RemoteException;
import java.io.IOException;
import net.jini.admin.Administrable;
import org.jworkplace.account.Session;
public interface WorkProcess
274
{
public Session connect() throws RemoteException, IOException;
public void disconnect() throws RemoteException, IOException;
public void updateTask(Task item) throws RemoteException,
IOException;
public Task[] getTasksAssigned(Resource resource) throws
RemoteException;
}
The WorkProcess Implementation
The WorkflowService also implements the RemoteWorkProcess interface, although
this would not have to be the case. A more complete implementation would separate the Process
Definition interface from the Process Control interface.
//
// WorkProcess Interface
//
// connect to the workflow engine
public Session connect(UUID processId) throws RemoteException
{
return new Session();
}
// disconnect from the workflow engine
public void disconnect(Session session) throws RemoteException
{
session.isActive(false);
}
// Determine if the task is still valid
public void updateTask(Task task) throws RemoteException
{
ProcessDef processDef = null;
// Determine if the task is still valid
if(!processMap.containsKey(task.processId)) {
return;
}
// update the task list for the worker
updateTaskList(task);
// get the associated work item
processDef = (ProcessDef)processMap.get(task.processId);
WorkItem item = processDef.getWorkItem(task.taskId);
// update the StarEntry
write(item, task);
}
private void updateTaskList(Task task) {
List taskList = null;
Task item = null;
try {
// find the task in the task list
if(userMap.containsKey(task.user)) {
taskList = (List)userMap.get(task.user);
Iterator itr = taskList.iterator();
while(itr.hasNext()) {
275
item = (Task)itr.next();
if(item.taskId.equals( task.taskId)) {
int pos = taskList.indexOf(item);
// update the task to reflect any changes
taskList.set(pos,task);
}
}
}
}
catch(Exception e) {
e.printStackTrace(); }
}
// called by clients to get task assingments
public Task[] getTasksAssigned(Resource resource) throws
RemoteException {
List taskList = null;
try {
// assume resource contains worker
String worker = resource.getResource();
if(!userMap.containsKey(worker)) return null;
// get the tasklist associated with this worker
taskList = (List)userMap.get(worker);
} catch(Exception e) { e.printStackTrace();
return null; }
return (Task[])taskList.toArray(new Task[0]);
}
//
// Use the StarInterface proxy to interface with JavaSpaces
//
private synchronized void write(WorkItem item, Task task) {
try {
if(space == null) {
System.out.println("WorkflowService::write
Lost in space...unable to
continue");
shutdown();
}
// Use the item and task information
// to create a StarEntry description
Time time = item.activityTime();
String resource = item.getAssignedTo();
StarEntry entry = new StarEntry(task.processId,
task.taskId,
time.getStartTime(),
time.getStopTime(),
item.description,
task.status,
resource);
// Write the StarEntry to JavaSpaces
space.write(entry);
}
catch (Exception e) {
e.printStackTrace(); }
}
Discovering Dependent Services
276
The implementation of the DiscoveryListener interface resolves references to the Star
Service and the MailboxManager. There is no guarantee that the services will be active
when the WorkflowService starts. If you fail to find either service, use the notify
method of the ServiceRegistrar to notify you when the service joins the community:
//
// DiscoveryListener Interface
//
public synchronized void discarded(DiscoveryEvent e) { }
public synchronized void discovered(DiscoveryEvent de) {
// get the array of lookup services discovered
ServiceRegistrar[] registrars = de.getRegistrars();
// find JavaSpace proxy
findStarInterface(registrars);
// find our mailbox manager
findMailboxManager(registrars);
// found everything we need terminate discovery
if(space != null && mailbox != null)
mgt.terminate();
}
// loop through discovered service registrars
private void findStarInterface(ServiceRegistrar[] registrars) {
if(space == null) {
try {
Class[] cls = new Class[]
{ Class.forName(starInterfaceClass) } ;
ServiceTemplate template = new ServiceTemplate(null, cls,
null);
for(int i=0; i < registrars.length; i++) {
space = (StarInterface)findService(registrars[i],
template);
if(space != null) {
break;
}
}
} catch (Exception e) { e.printStackTrace(); }
}
}
// loop through discovered service registrars
private void findMailboxManager(ServiceRegistrar[] registrars) {
if(mailbox == null) {
try {
Class[] cls = new Class[]
{ Class.forName(mailboxManagerClass) } ;
ServiceTemplate template = new ServiceTemplate(null, cls,
null);
for(int i=0; i < registrars.length; i++) {
mailbox = (MailboxManager)findService(registrars[i],
template);
if(mailbox != null) {
break;
}
}
277
}
catch (Exception e) {
e.printStackTrace(); }
}
}
// if unable to find service register for notification
private synchronized Object findService(ServiceRegistrar
registrar,
ServiceTemplate template)
throws RemoteException {
Object service = null;
try {
service = registrar.lookup( template );
} catch ( RemoteException ex ) {
ex.printStackTrace();
}
// TRANSITION_NOMATCH_MATCH to indicate when template match
occurs
// generate remote event
if ( service == null ) {
registrar.notify(template,
ServiceRegistrar.TRANSITION_NOMATCH_MATCH,
(WorkflowBackend)this, null,
Long.MAX_VALUE);
}
return service;
}
//
// ServiceEvent Listener
//
public synchronized void notify(RemoteEvent event)
throws UnknownEventException,
RemoteException
{
// If service event received determine which service
// has joined the community
if(event instanceof ServiceEvent) {
ServiceEvent serviceEvent = (ServiceEvent)event;
ServiceItem item = serviceEvent.getServiceItem();
if(item.service instanceof StarInterface) {
space = (StarInterface)item.service;
}
else if(item.service instanceof MailboxManager) {
mailbox = (MailboxManager)item.service;
}
}
}
The WorkflowEngine Implementation
The WorkflowEngine is responsible for the following:
•
•
•
Creating a space listener for CompleteTask state objects
Creating a task for each WorkItem in the process definition and updating the task
list for each worker
Registering the workers with the MailboxManager if they have not already
registered with the service
Listing 11.13 The WorkflowEngine Class
278
// Internal class of WorkflowService
private class WorkflowEngine extends Thread {
public WorkflowEngine() {
super();
start();
}
public void run() {
try {
while (!isInterrupted()) {
// wait for process to be started
UUID uuid = getProcessId();
// create a task completion listener for this
process
createProcessListener(uuid, new CompleteTask(uuid));
// create all tasks to activate
activateProcess(uuid);
}
}
}
catch (Exception e) {
finally { }
e.printStackTrace();
}
private void activateProcess(UUID processId) {
ProcessDef processDef =
(ProcessDef)processMap.get(processId);
try {
WorkItem[] items = processDef.getWorkItems();
for(int i=0; i<items.length; i++) {
System.out.println("Engine activateProcess: " +
items[i].getId());
// create a Task for each WorkItem
WorkItem item = (WorkItem)items[i];
String processName = processDef.getProcessName();
Task task = createTask(processId, processName, item);
// update the task list for the worker
if(task != null)
updateTaskList(task);
// write a StarEntry for each activity
write(items[i], task);
}
}
catch (Exception e) {
e.printStackTrace(); }
}
private Task createTask(UUID processId, String processName,
WorkItem item)
{
Task task = null;
try {
String taskId = item.getId();
String worker = item.getAssignedTo();
task = new Task(processId, processName, taskId, worker,
279
ActivityState.TASK_INIT);
// does the worker already have a mailbox
RemoteEventListener listener =
mailbox.getListener(worker);
if(listener == null) {
// register the worker
mailbox.register(worker, Long.MAX_VALUE);
// create the registration template for the worker
StarEntry entry = new StarEntry();
entry.resource = item.getAssignedTo();
// get the mailbox for worker
listener = mailbox.getListener(worker);
MarshalledObject handback = new
MarshalledObject(worker);
// set the worker mailbox to get space
notifications
space.setRemoteEventListener(entry, handback,
listener);
}
}
catch (Exception e) {
e.printStackTrace(); }
return task;
}
private synchronized void updateTaskList(Task task)
{
try {
String worker = task.user;
if(!userMap.containsKey(worker)) {
List taskList = new ArrayList();
userMap.put(worker, taskList);
}
List taskList = (List)userMap.get(worker);
taskList.add(task);
} catch (Exception e) { e.printStackTrace(); }
}
}
// end of WorkflowEngine
The TaskListener (RemoteEventListener)
The TaskListener is a service side listener that has been registered by the workflow engine
for each process defined in the workflow system. It is notified when CompleteTask objects
are written to space:
Listing 11.14 The TaskListener Class
package org.jworkplace.workflow;
import
import
import
import
import
java.rmi.RemoteException;
java.rmi.MarshalledObject;
java.rmi.server.UnicastRemoteObject;
java.io.IOException;
net.jini.core.event.RemoteEventListener;
280
import net.jini.core.event.RemoteEvent;
public class TaskListener implements RemoteEventListener {
private RemoteWorkProcess engine;
private StarInterface space;
public TaskListener(RemoteWorkProcess workProcess,
StarInterface starInterface) throws
RemoteException
{
this.engine = workProcess;
this.space = starInterface;
UnicastRemoteObject.exportObject(this);
}
// this should create a new thread
public void notify(RemoteEvent event) {
try {
CompleteTask template = new CompleteTask();
while(template != null) {
// for each complete task take from space
template =
(CompleteTask)space.takeStarIfExists(template);
if(template != null) {
// the handback will contain the Process Definition
MarshalledObject handback =
event.getRegistrationObject();
if(handback != null) {
ProcessDef processDef = (ProcessDef)handback.get();
// Get the successor task
WorkItem item =
processDef.getNextItem(template.taskId);
if (item != null) {
// Create a task template
String processName =
processDef.getProcessName();
String taskId = item.getId();
String worker = item.getAssignedTo();
Task task = new Task(template.processId,
processName,
taskId,
worker,
ActivityState.TASK_READY);
// Put the next task in a ready state
engine.updateTask(task);
}
}
}
}
}
catch (Exception e) {
e.printStackTrace(); }
}
}
The MailEventHandler
281
The MailEventHandler is a client-side listener that has been registered by the client user
interface for the user logged in to the workflow system. It receives notifications when a
StarEntry is written that matches the user ID of the user connected to the system.
Listing 11.15 The MailEventHandler Class
package org.jworkplace.mailbox;
import java.rmi.RemoteException;
import java.rmi.RemoteEventListener;
import java.rmi.MarshalledObject;
import java.rmi.server.UnicastRemoteObject;
import net.jini.core.event.UnknownEventException;
import net.jini.core.event.RemoteEvent;
import org.jworkplace.workflow.WorkProcessManager;
import org.jworkplace.workflow.Resource;
import org.jworkplace.workflow.Activity;
import org.jworkplace.workflow.WorkItemModel;
import org.jworkplace.workflow.Task;
public class MailEventHandler extends UnicastRemoteObject implements
RemoteEventListener {
private EventView view;
private WorkItemModel model;
public MailEventHandler(EventView view, WorkItemModel model)
throws RemoteException
{
this.view = view;
this.model = model;
}
public synchronized void notify(RemoteEvent event)
throws UnknownEventException,
RemoteException
{
// the marshalled object contains the user id
try {
MarshalledObject object = event.getRegistrationObject();
String worker = (String)object.get();
// get the tasks assigned
Task[] items = model.getTasksAssigned(worker);
// update the user display
view.callback(items);
}
catch (Exception e) {
e.printStackTrace(); }
}
}
Content Management Workflow
For this example, a process for publishing health care content on the Web is defined as shown in
Figure 11.9. The process definition involves a number of activities, and each activity adds specific
content approval criteria and quality control to the publishing process.
Figure 11.9. A defined process flow for health care content publishing to the Web.
282
Note
The code, scripts, and instructions to run the Workflow system are available at
www.jworkplace.org.
Creating the Process Definition
Figure 11.10 shows how work items are created and assigned in our process definition user
interface. The Workflow UI has been integrated with the collaborative capabilities of the peer-topeer content management and instant messaging example from Chapter 10, "GroupWare."
Figure 11.10. Defining the Process Definition work items.
The Process Definition is entitled Health Care Content Publishing and is owned by the content
administrator. The draft document item (first activity) has been assigned to the writer. A short
descriptive instruction can be provided, and the scheduled start and completion times are recorded.
You would create an item for each activity in the process. The Create Process panel provides an
export function that publishes the process to the WorkflowService.
283
The Schedule Display
The schedule display (see Figure 11.11) shows all the work items that have been defined in the
process, the scheduled start and finish times, and the resources assigned.
Figure 11.11. A sample schedule for health care content publishing is displayed by
the schedule panel.
Although the display in Figure 11.11 shows 12:00 a.m. as the time of day for each task, this is
simply the default. Tasks can be scheduled for starting and completing on any time scale.
The Writer's Task Assignment Display
The example in Figure 11.12 is the result of the writer connecting to the EventMailbox
manager and receiving task assignments. The status column indicates that the two tasks assigned
to the writer in the Publishing process have yet to start.
Figure 11.12. When the writer connects to the workflow system, all the writer's
assigned tasks are displayed.
284
The writer indicates that a task has started by selecting the Activate button. When the task is
complete the Complete button is selected by the writer. This triggers an update to the remote
Workflow service, which writes an updated Task and CompleteTask entry. JavaSpaces
notifies the EventMailbox listener, which delivers the updated status to the editor who is
waiting to start the next task.
The Editor's Task Assignment Display
After the writer completes the first activity, the editor's display immediately reflects the change in
status (see Figure 11.13). The editor's review task is now available and can be worked.
Figure 11.13. The editor is notified when a task is in a "ready" status.
285
Summary
This chapter demonstrated that sophisticated collaboration and workflow processing are possible
with a minimum of effort when using Jini and Jini services.
The example presented provides a working collaborative workflow application. It can be used by
teams working together toward common goals. As mentioned, effective use of collaborative
workflow is now considered vital in the success of enterprises of all kinds.
You now have an application capable of the following:
•
•
•
Defining Process Definitions and work items
Scheduling tasks and assigning resources
Notifying participants when dependent tasks have completed and new tasks can start
This example should suggest many other ideas for building collaborative applications using Jini
and JavaSpaces. It can serve as a starting point for service composition in distributed workflow
processing.
286
Chapter 12. Agents
IN THIS CHAPTER
•
•
•
•
•
Agents of Collaboration
Simple Agent Frameworks
An Example Process Control Agent
Future Jini Agents
Summary
This chapter provides an overview of software agents and the mapping of agent requirements to
the Jini services. Demonstration of simple agents of collaboration in a development scenario is
provided. We will build on the exercises in Chapter 10, "GroupWare," and Chapter 11,
"Workflow," to automate communication between the development community.
Agents of Collaboration
Software agents always conjure up images of distant future sci-fi technologies; however, the
future is now! The popularity of the Internet has ushered in a new era of communication and
collaboration, fueled by an explosion of available content. Software agents are being developed to
provide a framework for organizing and interpreting the vast amount of information available on
the Internet. Agent development has also increased in popularity because the Web has become the
prominent channel for user and business communication. This popularity has also blurred the
distinction between traditional software and agent-based modules.
Agent technology has also been impacted by the growth of e-commerce. A new breed of software
agent is attempting to increase efficiency of transactions, across electronic markets and exchanges.
Often we think of business-to-business (B2B) and business-to-consumer (B2C) as two separate
models of participation and communication. However, B2B and B2C are not discrete models but
rather exist on a continuum. Agents can be used to acquire knowledge about customers, partners,
and product preferences. As a result, the capability to leverage software agents at key points, for
instance in distribution, manufacturing, sales, and customer relationship management, is
compelling.
Imagine a business engaged in distributing products and services over the web. A tremendous
amount of marketing research is involved in determining the effectiveness of distribution channels.
Many companies are looking for more effective data capture and analysis techniques. Software
agents provide a solution.
Jini and JavaSpaces can provide a foundation for building agent frameworks. The benefits include
the following:
•
•
•
•
•
•
Software mobility "out-of-the-box"
Distributed storage using JavaSpaces
Support for communicating agents using distributed events
A framework to find information, services, and agents
A framework to support transactions using the two-phase commit protocol
The capability to use cryptographic channels to communicate
The Jini network of loosely coupled collaborating processes is an ideal foundation for the global
competitive markets of the twenty-first century. As we have moved into the Information Age,
287
strategic advantage now lies not only in the acquisition of information but also in the knowledge it
represents. Progressive corporations are investing in technologies to help systems and information
workers become more effective in the capture, analysis, and dissemination of information (see
Figure 12.1).
Figure 12.1. Jini and RMI provide a compelling model for agent frameworks.
Jini and JavaSpaces hold great promise in allowing systems, agents, and information workers to
collaborate in new and exciting ways. These technologies will enable multidimensional
information exchange and intuitive workgroup formation.
Simple Agent Frameworks
So what is an agent and how does it differ from any other software program? The distinction and
refinement of what is and what is not an agent has generated a lot of press and discussion. A
considerable amount of effort has been focused on classifying types of software agents. For
instance, those that inhabit the physical world are called robots. Those that inhabit computer
networks are sometimes referred to as softbots, and those that perform specific tasks are
sometimes called taskbots.
Researchers often use role-based classification in an attempt to elucidate agent definition.
Accordingly we have search agents, navigation agents, management agents, domain-specific
agents, as well as development and help agents.
Although the line between agent and software program can be gray, other characteristics of agents
help to delineate the distinction:
•
•
•
Learning—The capability for a software program to acquire knowledge without
reprogramming
Goal-oriented direction—The capability of a software program to be instructed as to a
desired result and then be able to determine the accuracy of the attained result
Community—The capability for software to be able to communicate with other software
to emulate knowledge acquisition and goal-oriented behavior
288
Hyacinth Nwana in the paper "Software Agents: An Overview" provides a typology for
classifying agents. Three attributes or characteristics of agents are identified: autonomy, learning,
and cooperation (see Figure 12.2).
Figure 12.2. A model for classifying software agents.
Autonomous agents act on behalf of the user and exhibit a level of independence from user
guidance. Learning agents are capable of "acquiring" knowledge using the resources in their
environment. Cooperating agents are capable of interacting with other agents using common
semantics, communication language, or ontology.
Four types of agents result from this classification: smart, collaborative and learning, collaborative,
and interface. However, only smart agents exhibit and aggregate autonomy, learning, and
cooperational behavior as shown in Figure 12.2.
Mobile Agents
Autonomous agents are often defined as systems that can act on behalf of a user and exhibit
capabilities to learn and cooperate with their environment. Typically, an agenda or goal drives an
agent's behavior. A mobile agent can move across machines and networks to carry out this mission.
There is a significant amount of work being done to provide mobile agents for network
management. It is easy to envision the benefits gained by network managers using mobile agents
to relay conditions and instructions to network elements.
Jini provides a foundation for many of the requirements for mobile agents. As discussed
previously, the capability to move code from machine to machine through downloadable proxies
is a key enabler of the Jini framework.
You can quickly define an interface for a remote agent and a mobile agent by exploiting RMI
interface semantics.
For instance, extending Remote provides an interface that is accessed remotely through a stub
reference by the caller.
289
public interface RemoteAgent extends java.rmi.Remote {
public void tell(Sender sender, Receiver receiver, Content
content)
throws RemoteException;
}
On the other hand, extending Serializable provides a mechanism to move the object to the
caller.
public interface MobileAgent extends java.io.Serializable {
public void tell(Sender sender, Receiver receiver, Content
content)
throws RemoteException;
}
You can even pass references from caller to receiver and implement powerful callback
mechanisms.
Personal Assistants
A Personal Assistant is a software agent that acts semi-autonomously for and on behalf of a user.
Often it accomplishes routine support tasks to allow the user to concentrate on more important
activities. It is unobtrusive but ready when needed and possesses knowledge about the user and
her areas of work.
The notion of a Personal Assistant is broad. A Personal Assistant can be used for any of the
following tasks:
•
•
•
•
•
•
Managing a user's calendar
Filtering and sorting e-mail
Managing a user's activities, plans, and tasks
Locating and delivering information
Purchasing items
Planning travel
Henry Lieberman, of the Media Laboratory at the Massachusetts Institute of Technology,
developed a Web browsing agent called Letizia. It assists users in navigating the voluminous
content of the Web. His focus has been on combining two popular trends in agent development:
"Interface agents, software that actively assists a user in operating an interactive interface, and
autonomous agents, software that takes action without user intervention and operates concurrently,
either while the user is idle or taking other actions."
So, let's continue building on the collaboration capabilities of the last two chapters. You will build
an agent capable of communicating with a user regarding the status of assigned tasks.
An Example Process Control Agent
One function of a Personal Assistant is to help a user manage tasks and control the scheduling of
activities. Calendaring agents assist with the management of a user's calendar, such as scheduling
appointments and sending reminders and meeting alerts. Often this type of agent works in the
background, monitoring a user calendar and triggering alert notifications.
290
The Process Control agent defined in this example is similar to a Personal Assistant; however, it is
used to monitor task progress and alert the user through notification messages. In addition, it
demonstrates the following:
•
•
The LeaseRenewalService as a framework for resource monitoring
JavaSpaces as a platform for distributed communication
The Workflow Service defined in Chapter 11 provides the necessary process and task definition
for the Process Control Agent (see Figure 12.3).
Figure 12.3. Agent process monitoring using the Lease Renewal Service.
Monitoring Process and Task Definition
When a new process is defined, a ProcessEntry is written to JavaSpaces by the
WorkflowService (see Listing 12.1).
Listing 12.1 The ProcessEntry Class
package org.jworkplace.workflow;
import java.rmi.RemoteException;
import net.jini.entry.AbstractEntry;
import com.sun.jini.proxy.UUID;
public class ProcessEntry extends AbstractEntry implements
ActivityState {
291
public UUID processId;
public ProcessEntry() {
this(null); }
public ProcessEntry(UUID processId) {
this.processId = processId;
}
public
public
public
public
void
void
void
void
start() throws RemoteException { }
terminate() throws RemoteException { }
suspend() throws RemoteException { }
resume() throws RemoteException { }
}
Similar to the TaskEntry (see Chapter 11), the ProcessEntry extends
AbstractEntry and implements the ActivityState interface. This class might be used
as a base class for future process customization. The main point to note in this example is the
initialization of the ProcessEntry with the unique process identifier.
The AgentService creates a single process listener. A ProcessListener is registered
to receive notification of process entries written to JavaSpaces in Step 1(see Figure 12.3). When a
new process is defined the WorkflowService writes a ProcessEntry to space as in
Step 2. The notify method of the ProcessListener is invoked in Step 3. The
ProcessListener delegates the notification event to the AgentService (Step 4 in the
diagram shown in Figure 12.3).
The AgentService creates an associated process monitor. The process monitor is used to
alert a user agent of a task-in-trouble. A task-in-trouble is a task that has not completed and is
scheduled to complete within a certain time frame or "window." Although this example does not
focus on task-in-trouble or risk assessment definition, it is clear that the framework can be used to
trigger notifications based on any risk assessment strategy that may be appropriate.
The AgentService removes the ProcessEntry (Step 5) from JavaSpaces and creates a
LeaseRenewalSet, as shown in Step 6. The AgentService provides a mapping
between process definitions and an associated LeaseRenewalSet. Per-process lease
management is provided through the AgentService interface. In other words, the
AgentService provides a common interface for the management of leases during the lifetime
of an active process. The AgentService creates a LeaseRenewalSet by invoking the
createLeaseRenewalSet method of the LeaseRenewalService (LRS). The Sun
lease renewal service "norm" is an example implementation.
Monitoring Process and Task Completion
As discussed in Chapter 7, "The Helper Services," the LRS is a helper service that can be
employed by both Jini clients and services to perform all lease renewal duties on their behalf (see
Figure 12.4).
Figure 12.4. The AgentService uses the Jini Lease Renewal Service to manage
leases and provide an early warning notification system for monitoring workflow
activity.
292
The LRS enables client-using entities to remain inactive until they are needed and can request that
the lease renewal service take on the responsibility of renewing the leases granted to the service.
This enables the service to be dormant without risking the loss of access to resources. In the
AgentService you are also using the LRS as a mechanism for generating time-based events.
The LeaseRenewalSet is itself a leased resource with special semantics. The
setExpiration WarningListener (Step 7 in Figure 12.3) method of the
LeaseRenewalSet, is used to set a RemoteEventListener notified prior to the
expiration of the lease renewal set lease in Step 8. This method takes a long parameter that
indicates to the lease renewal set the number of milliseconds prior to expiration to generate a
warning event. The RemoteEvent is an instance of ExpirationWarningEvent,
which can be used to retrieve the lease associated with the lease renewal set. The following code
fragment demonstrates the necessary code to retrieve the Lease associated with the expiring
lease renewal set:
public synchronized void notify(RemoteEvent event)
throws UnknownEventException,
RemoteException
{
try {
if(event instanceof ExpirationWarningEvent) {
ExpirationWarningEvent evt =
(ExpirationWarningEvent)event;
Lease lease = evt.getRenewalSetLease();
In addition to the lease, the event notification can contain a MarshalledObject, if it is
passed during registration of the expiration warning listener. The
getRegistrationObject method of the remote event is used to retrieve the handback
object.
MarshalledObject handback = evt.getRegistrationObject();
The expiration time of the lease renewal set is set to coincide with the completion time of the next
work item in the process definition. As you may recall, the work items in the process definition
are sorted according to start time. In the AgentService example, you will create a simple
293
single dependence graph of tasks. In other words, a single predecessor task must complete before
a successor task can start. Of course more complex task graphs should be supported in production
systems. The stop time of each work item is used to generate an expiration time for the lease
renewal set. The expiration time is an adjusted stop time value. The computeWarningTime
method of the AgentService is used to generate the notification value. The lease renewal set
uses the value to trigger a remote event to the registered ProcessListener, which invokes
the Agent value. The lease renewal set uses the value to trigger a remote event to the registered
ProcessListener, which invokes the Agent Service's monitorAlert method (see
Figure 12.4).
The following code fragment from ProcessListener simply checks the type of event and
invokes the appropriate agent method.
public synchronized void notify(RemoteEvent event)
throws UnknownEventException,
RemoteException
{
try {
// is a lease renewal set warning being triggered
if(event instanceof ExpirationWarningEvent) {
// activate monitor alert
ExpirationWarningEvent evt =
(ExpirationWarningEvent)event;
// get the lease for the lease renewal set
Lease lease = evt.getRenewalSetLease();
// get the hanback
MarshalledObject handback = evt.getRegistrationObject();
agentService.monitorAlert(lease, handback);
// assume a new process has been defined
} else {
// create lease renewal set and activate monitor for new
process
agentService.activateMonitor(event);
}
}
catch (Exception e) {
e.printStackTrace(); }
}
The activateMonitor method of the AgentService is used to retrieve the
ProcessEntry and the associated ProcessDef. The getRenewalTime sets the lease
expiration for the lease renewal set to the completion time of the first task in the process. The
computeWarningTime provides the advance notification mechanism. This triggers the
notify method (refer to Step 8 in Figure 12.3).
public void activateMonitor(RemoteEvent event) {
try {
// get the process to monitor
ProcessEntry template = new ProcessEntry();
ProcessEntry entry = (ProcessEntry)
space.takeStar(template);
// get the process definition
ProcessDef processDef =
workflowManager.getProcessDef(entry.processId);
// get lease set renewal time
long renewalTime = getRenewalTime(processDef, 0);
294
// are there tasks to monitor for this process
if(renewalTime > 0) {
// create a lease set for this process
LeaseRenewalSet leaseRenewalSet =
leaseRenewalService.createLeaseRenewalSet(renewalTime);
// create the handback
MarshalledObject handback =
new MarshalledObject(new
AgentProcessControl(entry.processId));
// activate process monitor
setMonitor(leaseRenewalSet, computeWarningTime(),
handback);
// update the processMap
processMap.put(entry.processId, leaseRenewalSet);
}
}
catch (Exception e) {
e.printStackTrace(); }
}
The monitorAlert method (Step 9 in Figure 12.3) uses the registration handback
(AgentProcessControl) to identify the process and work item that triggered the warning
expiration. This method determines the next work item to monitor and updates the associated lease
renewal set. If all tasks are complete, the lease on the lease renewal set is cancelled, and the
process is removed from the agent monitor:
public void monitorAlert(Lease lease, MarshalledObject handback) {
try {
// get the process and update the expiration time
AgentProcessControl processControl =
(AgentProcessControl)handback.get();
UUID processId = processControl.processId;
int index = processControl.index;
// get the process definition
ProcessDef processDef =
workflowManager.getProcessDef(processId);
// get lease set renewal time
long renewalTime = getRenewalTime(processDef, index);
// get lease renewal set
LeaseRenewalSet leaseRenewalSet =
(LeaseRenewalSet)
processMap.get(processId);
// are there tasks to monitor for this process
if(renewalTime > 0) {
// update the lease on the renewal set
lease.renew(renewalTime);
// update the handback
processControl.increment();
handback = new MarshalledObject(processControl);
295
// update the monitor
setMonitor(leaseRenewalSet, computeWarningTime(),
handback);
}
else {
// destroy the monitor
lease.cancel();
// remove the process
processMap.remove(processId);
}
}
catch (Exception e) {
e.printStackTrace(); }
}
The setMonitor method invokes the setExpirationWarningListener of the
lease renewal set. In the preceding example, you created a single process listener associated with
multiple renewal sets. Of course you could define a process listener per renewal set. This would be
the desirable choice as your system becomes more complex but would incur more resource
overhead. In other words, you would be exporting a process listener for every process defined in
the system. However, this would be a more flexible solution. The intent here is to demonstrate the
lease renewal service as a lease management service in the context of process management and
agent event notification.
public void setMonitor(LeaseRenewalSet leaseRenewalSet,
long expirationTime, MarshalledObject handback)
throws UnknownEventException,
RemoteException {
try {
leaseRenewalSet.setExpirationWarningListener(getWarningEvent
Listener(),
expirationTime, handback);
} catch (Exception e) { e.printStackTrace(); }
}
In addition, the AgentService communicates an alert to the user assigned to the task being
monitored. The AgentService uses the StarInterface proxy to write alert messages to
JavaSpaces.
The chat message techniques described in Chapter 5, "JavaSpaces Service," are used to create a
channel for communicating with the user.
Channels of Communication
JavaSpaces provides a platform for loosely coupled communication. It enables distributed
processes to communicate by exchanging entries or serialized data streams. Collaborating agents
require this type of communication capability, in addition to supporting common semantics, for
information exchange (see Figure 12.5).
Figure 12.5. An Interpreter is required to translate between different agent dialects.
Interpreters can be "chained" to form a Web of communication.
296
Although JavaSpaces can provide the underlying framework for communication, interfaces must
be defined to represent the common information exchange.
Knowledge Query and Manipulation Language (KQML)
The External Interfaces Working Group is a collection of artificial intelligence and distributed
systems researchers interested in software systems of communicating agents. The group was
formed in 1990 with the charter to develop protocols for the exchange of represented knowledge
among autonomous information systems. The result of this effort is KQML, the Knowledge Query
and Manipulation Language. KQML is a language and protocol to support communication
between (intelligent) software agents.
KQML defines messages or speech acts. A KQML message is also called a performative. A
performative is expressed as an ASCII string (see Figure 12.6). Agents exchange statements in
two categories: beliefs and goals. Beliefs represent the knowledge the agent has about its
environment. The goals represent the processing states the agent wants to attain or achieve.
Together this information comprises the agent's view of the world and is represented as a
knowledge base. Communication between agents is achieved by querying and manipulating the
contents of each agent's knowledge base. Additional information on KQML can be found at
www.cs.umbc.edu/kqml/.
Figure 12.6. KQML represents the communication between collaborating agents as
"speech acts." The speech act is encoded as an ASCII string and has a defined
structure. KQML has standard speech acts, called performatives, such as "tell"
and "monitor."
The implementation of an agent is not necessarily structured as a knowledge base. The
implementation may use a database system or a program using a special data structure. The only
297
requirement being that the system can translate the communication exchange into performatives,
which represent the virtual knowledge base of the agent.
Of course without common communication semantics, agents would be unable to exchange
information and coordinate activities. The standardization of information exchange is an important
area of agent research.
Natural Language Processing
Natural language processing (NLP) is an alternative to constraining an agent dialogue to a set of
predefined speech acts. NLP aims to support a more natural exchange of information by reflecting
the discourse as normal conversation. Most of this effort has been driven by improving human-tocomputer interaction.
As mentioned, although JavaSpaces can provide the underlying framework for communication,
standard interfaces must still be defined to represent the semantics of the exchange. KQML
performatives and NLP provide two possible approaches to standardizing agent communication:
the first (KQML), built on a standard definition of speech acts, and the latter (NLP) attempting to
process and understand human communication. Great strides have been made in recent years with
NLP systems. JavaSpaces could provide the common communication channel to integrate many
agent communication models.
Alert Notification
Although Jini and JavaSpaces do not provide a solution to knowledge representation or
standardizing agent communication, they do provide an underlying framework for communication.
The AgentService uses this framework to communicate process status between agents or
between agents and users (see Figure 12.7). You can use the StarService introduced in
Chapter 10, which provides the interface to JavaSpaces, to enable this communication channel.
Figure 12.7. The AgentService uses the StarService to establish a
communication channel with users and agents.
298
You can extend the ChatMessage introduced in Chapter 5 to define an AlertMessage.
The AlertMessage provides the notification information when a task is in trouble:
import net.jini.entry.AbstractEntry;
import org.jworkplace.chat.ChatMessage;
public class AlertMessage extends ChatMessage {
public AlertMessage(){ }
public AlertMessage(String channel, Integer position, String text)
{
super(channel, position, text);
}
}
The String parameter can be defined to express a speech act, such as that defined by KQML or
an NLP construct. The AlertMessage uses the channel to communicate with one or more
receivers of alert notifications. The receivers can be human, other software agents, or a
combination of both, as shown in Figure 12.8.
Figure 12.8. The agent service will generate an alert message for tasks that are in
trouble.
Future Jini Agents
299
As you study the Jini and JavaSpaces framework, you will discover many different areas where
agent behavior can fit. A few examples may help to provide future exploration.
Proxy Agents
A proxy agent could be a specialized type of Personal Assistant that learns not from user
keystrokes, but from monitoring remote service interaction. This would be beneficial in
discovering software interface usage patterns. Frequency of remote invocations would provide
additional insight into modeling performance-sensitive systems—for example, systems that are
bandwidth-constrained or resource-constrained in general. Proxy agents could also be used to
monitor service availability and interject intelligent routing or failover processing capabilities.
Discovery Agents
A discovery agent could help to manage the lookup process for clients and the join process for
services. An agent could learn operational characteristics. For example, it could rate services on
cost, availability, and reliability and determine communities of participation based on rules or
service entry inspection.
Service Capability Agents
As we move to composite services or service components, more reliance will be placed on
services cooperating and collaborating to provide features and functions. A capability agent could
be designed with the intent of mapping capabilities to platform constraints or the current state of
the network. For instance, a service currently not reachable might not necessitate total elimination
of a service offering but only partial disruption. A capability agent could learn by listening to
monitors and then alerting other agents in the system of the condition. Service factories could
listen for such notifications and temporarily build the appropriate interfaces.
Communication Agents
As mentioned earlier in this chapter, communication agents could help to resolve agent dialect
differences. Interpretive agents could bridge differences in knowledge base definitions or
broadcast requests for services that understand a specific ontology.
Summary
Agent technology has been affected by the growth of e-commerce. A new breed of software agent
is attempting to increase the efficiency of transactions across electronic markets and exchanges.
Three attributes or characteristics of agents are autonomy, learning, and cooperation.
A Personal Assistant is an agent that helps a user manage tasks and control the scheduling of
activities.
The Process Control agent defined in this chapter is similar to a Personal Assistant; however, it is
used to monitor task progress and alert the user through notification messages. In addition, it
demonstrates:
•
•
The LeaseRenewalService as a framework for resource monitoring, and
JavaSpaces as a platform for distributed communication.
300
We are still far from Hal, of 2001: A Space Odyssey fame, but we are moving quickly to practical
application of agents assisting our business, personal, and recreational activities in the digital
world.
Jini and JavaSpaces provide a platform for agent frameworks that includes the following:
•
•
•
•
Software mobility "out-of-the-box" to support mobile agents
Improved security based on the Java sandbox
Distributed storage using JavaSpaces for building virtual knowledge bases and
simplifying distributed communication
A framework that provides dynamic discovery of information, services, and agents
Many obstacles still will need to be addressed to build the "smart agents" mentioned at the
beginning of this chapter. But Jini does present an approach for building many practical
applications today.
301
Chapter 13. Jini as a Web Service
IN THIS CHAPTER
•
•
•
•
•
•
Business-to-Business E-commerce
What Are Web Services?
Simple Object Access Protocol (SOAP) as a Transport Technology
Jini in the B2B Context
SOAP-to-Jini Integration
Summary
This chapter presents Jini in the context of the current trend toward XML-defined Web services. It
provides an overview of the primary components defining Web services, which include the
following:
•
•
•
•
The Simple Object Access Protocol (SOAP)
Web Service Definition Language (WSDL)
Universal Description, Discovery, and Integration (UDDI)
The Electronic Business XML initiative (ebXML)
The intent is to demonstrate how Jini can play a role in e-commerce evolution.
Business-to-Business E-commerce
Business-to-business (B2B) e-commerce has become the focus of many business and technology
efforts dominating the Internet today. B2B e-commerce can be defined as the transactions
businesses engage in to conduct finance, commerce, and trade using the Internet.
Many technology solutions have been promoted to support B2B, but the Web services architecture
appears to be the heavyweight contender. Why shouldn't it—being initiated by the likes of IBM,
Microsoft, and Ariba—with every major vendor on the planet participating in some fashion? Web
services are being promoted as the next logical extension to delivering software functionality over
the Internet. It is being projected that future applications will be built from Web services
dynamically selected at runtime based on their cost, quality, and availability.
What Are Web Services?
A Web service is a collection of functions packaged and published to a network for use by other
programs and users. Web services result from software components being assembled and
discovered by service providers and service consumers. The intent is to provide a level of
integration standardization in making products and services available worldwide. The definition
takes on more substance when combined with the technologies defining it.
Web services promote the following standards:
•
•
SOAP (Simple Object Access Protocol) initially using HTTP or SMTP to standardize
message exchange
WSDL (Web Service Definition Language)—A standard service definition protocol
302
•
UDDI (Universal Description, Discovery, and Integration)—A standard service discovery
protocol
The W3C and members of key organizations—IBM and Microsoft—in Web service development
envision an end-to-end view to Web service definition. This can be demonstrated as three
complementary architectural stacks (see Figure 13.1). The wire stack provides the "on-the-wire"
view of the supporting message exchange. The description stack enables business requirements to
be mapped to technical specifications. The discovery stack provides the advertisement and
categorization services necessary for business entities to publish and find service definitions.
Figure 13.1. The architectural stacks of Web services.
Let's look at the major components to Web services in more detail and in the process map those
components to similar entities within the Jini model.
Simple Object Access Protocol (SOAP) as a Transport
Technology
The Simple Object Access Protocol (SOAP) is used to enable interoperability between vendor
implementations. SOAP is a lightweight protocol for the exchange of information in a
decentralized, distributed environment. It is an XML-based protocol that consists of three parts:
•
•
•
An envelope that defines a framework for describing what is in a message and how to
process it
A set of encoding rules for expressing instances of application-defined data types
A convention for representing remote procedure calls and responses
The following example is a request for a stock quote from a stock quoting Web service.
Example: SOAP Request and Response using HTTP POST from the W3C Specification at
www.w3.org/TR/SOAP/.
Listing 13.1 SOAP Request
POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
303
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
SOAPAction: "Some-URI"
<SOAP-ENV:Envelope xmlns:SOAPENV=http://schemas.xmlsoap.org/soap/envelope/
SOAPENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:GetQuote xmlns:m="Some-URI">
<symbol>DIS</symbol>
</m:GetQuote>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Example: SOAP Response
Listing 13.2 SOAP Response
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/
SOAPENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
<SOAP-ENV:Body>
<m:GetQuoteResponse xmlns:m="Some-URI">
<Price>34.5</Price>
</m:GetQuoteResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
SOAP can use HTTP as a transport protocol. By using HTTP, SOAP has access to most Webbased implementations today. This enables access to a vast array of resources, even through
firewalls, if the Web-based implementation is SOAP-compliant.
SOAP relies heavily on the namespace identifier for the elements and attributes defined in the
SOAP envelope.
xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/
In addition the SOAP encodingStyle attribute can be used to indicate the serialization rules
used in a SOAP message:
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
The attribute value is an ordered list of one or more URIs identifying the serialization rules that
can be used to deserialize the SOAP message indicated in the order of most specific to least
specific.
The SOAP Body element provides a mechanism for exchanging mandatory information intended
for the ultimate recipient of the message:
<m:GetQuote xmlns:m="Some-URI">
<symbol>DIS</symbol>
304
</m:GetQuote>
The SOAP Fault element is used to carry error and/or status information within a SOAP
message:
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:MustUnderstand</faultcode>
<faultstring>SOAP Must Understand Error</faultstring>
</SOAP-ENV:Fault>
Web Service Definition Language (WSDL)
WSDL is an XML language for describing Web services as a set of network endpoints that operate
on messages. A WSDL service description contains an abstract definition for a set of operations
and messages, a concrete protocol binding for these operations and messages, and a network
endpoint specification for the binding.
It is important to recognize that Web services are being defined to support different technology
platforms, operating systems, and programming languages. Although our discussion focuses on
Java, Web services may be defined for any system and language that supports the specification.
Thus the Microsoft .NET initiative using non-Java-based implementations should integrate with
IBM Java-based implementations. This is an important distinction from the Jini model of service
delivery, which assumes a Java-based interface.
WSDL allows the separation of the description of a service into two parts: the service interface
document and the service implementation document (see Figure 13.2). Information common to a
certain category of business services, such as message formats, interfaces, and protocol bindings,
are included in the service interface document. Information pertaining to a particular service
endpoint (that is, port definition) is included in the service implementation portion.
Figure 13.2. The WSDL's service description in two parts: the service interface
document and the service implementation document.
Example: WSDL Service Interface Document and Implementation Document from the W3C
Specification at www.w3.org/TR/WSDL/.
305
Listing 13.3 The WSDL Service Interface Document
<?xml version="1.0"?>
<definitions name="StockQuoteService-interface"
targetNamespace="http://www.getquote.com/StockQuoteServiceinterface"
xmlns:tns="http://www.getquote.com/StockQuoteService-interface"
xmlns:xsd=" http://www.w3.org/2001/XMLSchema "
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<documentation>
Standard WSDL service interface definition for a stock quote
service.
</documentation>
<message name="SingleSymbolRequest">
<part name="symbol" type="xsd:string"/>
</message>
<message name="SingleSymbolQuoteResponse">
<part name="quote" type="xsd:string"/>
</message>
<portType name="SingleSymbolStockQuoteService">
<operation name="getQuote">
<input message="tns:SingleSymbolRequest"/>
<output message="tns:SingleSymbolQuoteResponse"/>
</operation>
</portType>
<binding name="SingleSymbolBinding"
type="tns:SingleSymbolStockQuoteService">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getQuote">
<soap:operation soapAction="http://www.getquote.com/GetQuote"/>
<input> <soap:body use="encoded" namespace= "urn:single-symbolstock-quotes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded" namespace="urn:single-symbol-stockquotes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
</definitions>
A service interface document can reference another service interface document using an import
element. For example, a service interface that contains only the message and portType
elements can be referenced by another service interface that contains only bindings for the
portType.
A service implementation document contains a description of a service that implements a service
interface. At least one of the import elements will contain a reference to the WSDL service
interface document. A service implementation document can contain references to more than one
service interface document.
Listing 13.4 WSDL Service Implementation Document
306
<?xml version="1.0"?>
<definitions name="StockQuoteService"
targetNamespace="http://www.getquote.com/StockQuoteService"
xmlns:interface="http://www.getquote.com/StockQuoteServiceinterface"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<documentation>
This service provides an implementation of a standard
stock quote service. The Web service uses the live
stock quote service provided by XMLtoday.com.
The XMLtoday.com stock quote service uses an
HTTP GET interface to request a quote, and returns
an XML string as a response.
</documentation>
<import namespace="http://www.getquote.com/StockQuoteServiceinterface"
location="http://www.getquote.com/wsdl/SQS-interface.wsdl"/>
<service name="StockQuoteService">
<documentation> Stock Quote Service </documentation>
<port name="SingleSymbolServicePort"
binding="interface:SingleSymbolBinding">
<documentation>Single Symbol Stock Quote
Service</documentation>
<soap:address location="http://www.getquote.com/
stockquoteservice"/>
</port>
</service>
</definitions>
The import element in a WSDL service implementation document contains two attributes. The
namespace attribute value is a URL that matches the targetNamespace in the service
interface document. The location attribute is a URL used to reference the WSDL document
that contains the complete service interface definition. The binding attribute on the port
element contains a reference to a specific binding in the service interface document.
The service interface document is developed and published by the service interface provider. The
service implementation document is created and published by the service provider. The roles of
the service interface provider and service provider are logically separate, but they can be the same
business entity.
A service implementation is published in a UDDI registry as a business service with one or more
binding templates. The business service is published by the service provider.
Jini does not rely on XML for service definition. As mentioned, Jini relies on Java interfaces. The
WSDL has no direct counterpart in Jini, other than both provide a mechanism to define service
interfaces. The UDDI, however, does resemble the lookup and discovery service to some extent.
Universal Discovery and Description Integration (UDDI)
UDDI provides a method for publishing and finding service descriptions. The UDDI data entities
provide support for defining both business and service information. Compare and contrast this to
the Entry objects augmenting service definition in the ServiceRegistrar.
The service description information defined in WSDL is complementary to the information found
in a UDDI registry. UDDI provides support for different types of service descriptions and does not
307
mandate WSDL. As a result, UDDI has no direct support (as of this writing) for WSDL or any
other service description mechanism. It is possible to define bridges between a UDDI registry and
a Jini LUS.
The UDDI defines three primary components:
•
•
•
A registry for service providers to publish services and products offered
A service broker that maintains the registry
A service requestor that uses the broker to find products and services
The service requestor must be able to locate a UDDI registry to gain access to the B2B network of
services. The Web services architecture defines three core services (see Figure 13.3):
•
•
•
The publish service enables the service provider to publish a service description, so the
service requestor can find, understand, and use the service.
The find/discovery service enables the service requestor to retrieve a service description
directly, or query the service registry for the type and description of the service required
for invocation.
The bind service enables a service requestor to invoke or initiate an interaction with the
service at runtime using the binding details in the service description.
Figure 13.3. These three operations—publish, find, and bind—are core to the Web
services model.
The service provider is the platform that hosts access to the Web service. This is typically the
business process owner.
The service requestor is the application searching for and invoking or initiating an interaction with
a service. This can be a person or a program—for example, another Web service. Service
requestors can search by industry code, products and services offered, and geographic location.
The service registry is a searchable registry of service descriptions where service providers
publish their service definitions. Service requestors find services and obtain binding information
(in the service descriptions) for services during service development or during execution.
UDDI defines four basic data elements within the data model as shown in Figure 13.4. In version
1.0, they are as follows:
•
•
•
•
businessEntity (modeling business information)
businessService (high-level service description)
tModel (modeling a technology type or service type)
bindingTemplate (mapping between businessService and tModels)
308
Figure 13.4. UDDI provides a model for business and technical information.
The businessEntity provides information about a business and can contain one or more
businessServices. The business is the service provider. The technical and business
descriptions for a Web service are defined in a businessService and its
bindingTemplates. Each bindingTemplate contains a reference to one or more
tModels. A tModel is used to define the technical specification for a service.
The individual facts about a business, its services, its technical information, or information about
specifications for services are accessed individually by way of unique identifiers, or keys. A
UDDI registry assigns these unique identifiers when information is first saved, and these
identifiers can be used later as keys to access the specific data instances on demand. Each unique
identifier generated by a UDDI registry takes the form of a Universally Unique ID (UUID). This
is similar to the process of assigning a UUID by the LUS to a service in the service registrar.
The businessEntity structure contains one or more unique businessService
structures. Similarly, individual businessService structures contain specific instances of
bindingTemplate data, which in turn contains information that includes pointers to specific
instances of tModel structures.
From an XML standpoint, the businessEntity is the top-level data structure that
accommodates holding descriptive information about a business or entity. Service descriptions and
technical information are expressed within a businessEntity by a containment relationship.
The primary role that a tModel plays is to represent a technical specification. An example might
be a specification that outlines wire protocols, interchange formats, and interchange sequencing
rules.
UDDI Web Service Categorization
Categorization and the capability to assign category information to data is fundamental to the
UDDI registry. Categorization takes the form of classifying information related to some wellknown industry, product, or geographic categorization code set (see Figure 13.5). The intent is to
improve the capability to locate data within the UDDI registry for a specific purpose. However, it
is impractical to assume that the UDDI registry will be useful for general-purpose business search
309
due to the size of the registry database. It is envisioned that higher level services will be defined to
accommodate specific classifications or ontology representations.
Figure 13.5. The UUDI "cloud" is a set of distributed—replicated—nodes of UDDI
registries. These nodes provide the base definitions and categorization of Web
services—for example, industry, product, and geography.
The UDDI business registry is a logically centralized, physically distributed service with multiple
root nodes that replicate data with each other regularly. After a business registers with a single
instance of the business registry service, the data is automatically shared with other UDDI root
nodes and becomes freely available to anyone who needs to discover what Web services are
exposed by a given business. This again provides some comparison to the role of the LUS in terms
of propagation of service information. However, the dynamic capabilities of service discovery
within the Jini architecture are far more flexible and adaptable to dynamic changes in domain
space.
It is projected that public and private UDDI nodes will be defined. The UDDI Operator nodes
form the operator cloud facility that can be accessed from www.uddi.org. This represents the
public nodes of the UDDI network.
Private nodes between partner systems and within large corporations may also surface—for
example, e-marketplace UDDI nodes for finding Web services and for doing business within a
particular e-marketplace or industry; portal UDDI nodes supporting transactional Web presence
and for restricting access and monitoring usage; internal integration nodes to bridge Web presence
with back-end service delivery.
As expected, there are complementary and competing views to the definition of end-to-end ecommerce.
310
Electronic Business XML (ebXML)
The Electronic Business XML (ebXML) initiative is being defined by the United Nations body for
Trade Facilitation and Electronic Business (UN/CEFACT) and the Organization for the
Advancement of Structured Information Standards(OASIS). They have also initiated a project to
standardize XML business specifications.
The most important components of the ebXML architecture are the Registry and the Repository
(see Figure 13.6). The Registry provides access services, information models, and reference
implementation information. The repository provides the physical back-end information store. The
repository contains Document Type Definitions (DTD) and schemas retrieved by the registry for
conducting e-commerce transactions using ebXML. The ebXML Messaging Service provides for
the exchange of ebXML messages between trading partners over various transports (SMTP,
HTTP/S, and FTP). Visit www.ebxml.org for more information.
Figure 13.6. ebXML is taking a top-down design to e-commerce definition. The
initiative complements and competes with the IBM/Microsoft Web service
definition.
Web services and ebXML recognize the importance of providing a structure and process to B2B
system integration.
Both approaches require the implementation of a well-known interface at a well-known location.
Each approach involves a registry or broker to resolve the following questions:
•
•
•
•
•
What services are available?
Who can provide the service?
Where is the service located?
How is the service invoked?
What are the technology requirements?
Both approaches use XML to define and specify the answers to these questions.
In addition, a considerable amount of effort is required to document the interface and to exchange
the necessary information to conduct B2B e-commerce.
Needless to say, definition overlap and divergent solutions have and will continue to occur. The
degree of divergence in solutions and implications has yet to be fully understood. Expect to see
software bridges that enable key component integration or coexistence in areas such as UDDI
Service Registry and ebXML Service Repository.
The overall architecture is still in flux, and the pace of change presents a challenge to early
adopters. There are divisions even within the Web services architecture as competitors try to
differentiate their tools and solutions.
311
Jini in the B2B Context
Like UDDI and ebXML, Jini supports the concept of discovering services. Conceptually, the
lookup service can be thought of as containing any number of service items. Each service item can
have attributes associated with it. For instance, like UDDI, they may include industry codes,
implementation details, access protocols, and geographic locations (see Figure 13.7). These
attributes can be used to refine the search for a specific service.
Figure 13.7. Like the UDDI registry, the lookup service can be augmented with
classification information by using service attributes.
In addition, a proxy for the lookup service is available that knows how to interface with the lookup
service (see Figure 13.8). The code is downloadable to the client requesting the service.
Downloadable service proxies are one of the key differences between other "yellow pages"-like
services and Jini. It is also one of the main reasons it depends on a particular programming
language.
Figure 13.8. UDDI and the Jini lookup service share "yellow page" concepts, but
Jini leverages RMI and the Java platform to provide a more robust interaction
model.
312
So, although the other approaches to service definition are being satisfied through agreed-on XML
document schemas, Jini discovery and lookup provides a dynamic alternative to finding and
interacting with B2B service providers. Jini itself could be used to bootstrap the process of finding
UDDI and ebXML registries as shown in Figure 13.9.
Figure 13.9. Bridging registries: UDDI-to-LUS.
SOAP-to-Jini Integration
Listing 13.5 demonstrates a SOAP client accessing a Web service. The Web service is actually a
proxy accessing the Jini lookup service. The SOAP client lists the services registered in a Jini
lookup service.
313
Listing 13.5 The LookupProxy Class
package chapter13;
import java.io.*;
import java.rmi.*;
import java.util.*;
import
import
import
import
import
import
import
import
import
import
net.jini.core.entry.Entry;
net.jini.core.lookup.ServiceID;
net.jini.core.lookup.ServiceItem;
net.jini.core.lookup.ServiceMatches;
net.jini.core.lookup.ServiceRegistrar;
net.jini.core.lookup.ServiceTemplate;
net.jini.discovery.DiscoveryEvent;
net.jini.discovery.DiscoveryListener;
net.jini.discovery.LookupDiscoveryManager;
net.jini.space.JavaSpace;
public class LookupProxy implements DiscoveryListener {
private String[] groups = new String[] { "" } ;
private Vector services = new Vector();
private StringBuffer buffer = new StringBuffer();
private
private
private
private
private
LookupDiscoveryManager ldm;
ServiceTemplate template;
JavaSpace space;
ServiceID serviceID;
ServiceItem si;
public LookupProxy() {
System.setSecurityManager(new RMISecurityManager());
}
// DiscoveryListener Implementation
// Simply build a list of services that match
// a supplied template
public synchronized void discovered(DiscoveryEvent de) {
emit("Discovered LUS: ");
ServiceRegistrar[] registrars = de.getRegistrars();
for(int i=0; i < registrars.length; i++) {
try {
emit("URL:
" + registrars[i].getLocator().toString());
emit("ID:
" + registrars[i].getServiceID());
String groups[] = registrars[i].getGroups();
emit("GROUPS: " + groups[0]);
ServiceMatches sm = registrars[i].lookup(template,
Integer.MAX_VALUE);
emit("Matching services found ------: " +
sm.totalMatches);
emit("");
for(int j=0; j < sm.items.length; j++) {
// Process each ServiceItem
if(sm.items[j].service != null) {
emit("Service --------------: " +
sm.items[j].service);
emit("ServiceID ------------: " +
sm.items[j].serviceID);
Entry[] entries = sm.items[j].attributeSets;
emit("Service attributes ---:");
314
for(int k=0; k<entries.length; k++) {
emit(entries[k]);
}
services.addElement(sm.items[j]);
emit("");
}
}
}
notifyAll();
catch(Exception e) {
e.printStackTrace(); }
}
}
public void discarded(DiscoveryEvent de) { }
public synchronized Object getService() {
while(services.size() == 0) {
try {
wait();
} catch (InterruptedException ex) { }
}
return ((ServiceItem)services.elementAt(0)).service;
}
// this is the method that we will expose to the SOAP client
public String getService(String service) {
ServiceTemplate template;
try {
// if no class name is provided match on all services
if(service == null) {
template = new ServiceTemplate(null, null, null);
} else {
// build a template to match on the given class name
Class[] types = {
Class.forName(service) } ;
template = new ServiceTemplate(null, types, null);
}
// set the template
this.template = template;
// start discovery
ldm = new LookupDiscoveryManager(groups,null,this);
// wait for discovery to find services
getService();
}
catch (Exception e) {
e.printStackTrace(); }
// return the list
return new String(buffer);
}
// build the list display
private void emit(Object str) {
buffer.append(str + System.getProperty("line.separator"));
}
}
315
The LookupProxy implements the DiscoveryListener interface. You should be
familiar with the technique applied here. The only thing to note is the getService method.
This is the method that your SOAP client invokes.
Web Service Deployment
To deploy this Java class as a Web service, compile it and make sure that it's in your Java Web
server's class path (assuming that you already have Apache SOAP 2.x installed and configured).
The following steps explain how to deploy the service and invoke it from SOAP. If you do not
have Apache SOAP, you can get it from the www.apache.org Web site. The installation
instructions and required software are available with the distribution. A number of jar files are
required, and the necessary configuration is explained in the installation instructions. This
example was tested using the Apache Tomcat Web server, which is also available from the
Apache site.
To deploy a Web service using Apache SOAP, you must create an Apache deployment descriptor
like the one in Listing 13.6.
Listing 13.6 The LookupDD.xml Deployment Descriptor
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="urn:jini-service-lookup">
<isd:provider type="java"
scope="Application"
methods="getService">
<isd:java class="chapter13.LookupProxy" static="false" />
</isd:provider>
<isd:faultListener>org.apache.soap.server.DOMFaultListener
</isd:faultListener>
</isd:service>
The Apache SOAP Service Manager uses the deployment descriptor to collect
information about deployed services. A few things to note:
•
•
•
id is the URI of the Web service.
methods are a list of the public methods you will expose to clients of the Web service.
class is the name of the Java class to invoke and must be on the classpath of your
servlet engine.
The command file in Listing 13.7 can be used to deploy and activate the Web service.
Listing 13.7 test.cmd Script File
@echo off
echo This test assumes a server URL of http://localhost:8081
/soap/servlet/rpcrouter
echo Deploying the lookup service...
java org.apache.soap.server.ServiceManagerClient
http://localhost:8081 /soap/servlet/
rpcrouter deploy
xml/LookupDD.xml
echo .
echo Verify that it's there
java org.apache.soap.server.ServiceManagerClient
http://localhost:8081 /soap/servlet/
rpcrouter list
316
echo .
echo Running the lookup test to find the EventMailbox service
java chapter13.SOAPClient
http://localhost:8081/soap/servlet/rpcrouter
net.jini.event.EventMailbox
echo .
echo Undeploy it now
java org.apache.soap.server.ServiceManagerClient
http://localhost:8081 /soap/servlet/
rpcrouter undeploy
urn:jini-service-lookup
echo .
echo Verify that it's gone
java org.apache.soap.server.ServiceManagerClient
http://localhost:8081 /soap/servlet/
rpcrouter list
The script uses the org.apache.soap.server.ServiceManagerClient to
deploy, list, and undeploy the Web service.
The SOAPClient class builds a SOAP-RPC and invokes the getService method. The
results of the invocation are displayed on the console. Listing 13.8 provides an example of the
SOAPClient class.
Listing 13.8 The SOAPClient Class
package chapter13;
import
import
import
import
import
java.io.*;
java.net.*;
java.util.*;
org.apache.soap.*;
org.apache.soap.rpc.*;
public class SOAPClient {
public static void main(String[] args) throws Exception {
// has a SOAP url been entered
if (args.length < 1) {
System.err.println ("Usage: java " + SOAPClient.class.getName
() +
" SOAP-router-URL service");
System.exit (1);
}
// set encoding style
String encodingStyleURI = Constants.NS_URI_SOAP_ENC;
// set the SOAP router url
URL url = new URL (args[0]);
String service = null;
// check if a specific interface has been requested
if(args.length == 2)
service = args[1];
// Build the RPC call.
Call call = new Call ();
317
// note the target URI was set in the deployment descriptor
call.setTargetObjectURI ("urn:jini-service-lookup");
// the method that was defined in the deployment descriptor
call.setMethodName ("getService");
// we are not doing any special encoding,
// for instance user defined object handling
call.setEncodingStyleURI(encodingStyleURI);
// set the parameters for the call
Vector params = new Vector ();
params.addElement (new Parameter("service", String.class, service,
null));
call.setParams (params);
// invoke the url and get the response
Response resp = call.invoke (url, "" );
// Check the response.
if (resp.generatedFault ()) {
Fault fault = resp.getFault ();
System.out.println ("The call failed: ");
System.out.println (" Fault Code
= " + fault.getFaultCode
());
System.out.println ("
Fault String = " + fault.getFaultString
());
}
else {
// no error display the result to the console
Parameter result = resp.getReturnValue ();
System.out.println (result.getValue ());
}
}
}
When you run the test.cmd script, you should see a list of services displayed to the console
that are registered in lookup services within the multicast radius.
Summary—The Road to (Dis)-Integration
As this chapter highlights, Web services are trying to bridge the diverse technologies and
implementations that exist in systems, networks, and applications today.
The Simple Object Access Protocol (SOAP) is used to enable interoperability between vendor
implementations by defining a common transport protocol.
WSDL is an XML language specification for describing Web services as a set of network
addresses and operations.
UDDI provides a method for publishing and finding service descriptions.
The Electronic Business XML (ebXML) initiative is also trying to standardize XML business
specifications.
318
The intent is to provide new levels of integration capabilities that are primarily Internet focused
but need not be. This has proven to be a formidable challenge in the past, to hardware and
software providers. Just take an audit of the integration technologies that exist in your current
environment. If you're a large organization, this can resemble a graveyard for technology
dinosaurs. An important question needs to be answered.
What integration technologies will you eliminate by embracing Web services? In other words, will
Web services simplify your environment or will they add layers of complexity?
Successful systems in the future should be built from a more homogenous structure. The key will
not be in the capability to support many different implementations. This is a combinatorial
problem that can quickly become complex and dilute the focus of development efforts.
The key will be in supporting "smarter" systems. Jini defines an environment that supports smarter
systems. Jini also provides flexibility in balancing diversity with the acceleration of information
access and the devices enabling pervasive computing—the new network reality.
319
Chapter 14. Pervasive Computing and Mobile
Devices
IN THIS CHAPTER
•
•
•
•
•
Jini Environment and Assumptions
Can Jini Scale Down?
Will Devices Scale Up?
The Network Reality—Pervasive Computing
Summary
Jini arrived on the technology scene with great momentum and excitement. Jini visionaries spoke
of a world where non-PC devices—PDAs, cell phones, life-enhancing appliances, and so on—
would proliferate across a global network. Interconnected devices would enable a new generation
of communication devices, information exchange, and pervasive computing. Now this is occurring.
The market for network-connected devices continues to grow. Whether these devices are new, or
just more powerful versions of existing products, all are becoming increasingly interconnected
across a network.
Like many expressions of future reality, the path to manifestation can be unpredictable. For
instance, when Java and Java Applets were introduced, the desktop machine became the first
logical target for deployment. However, Java quickly migrated to the middle-tier of application
architectures. This was unexpected by many because of performance and scalability concerns with
Java. What was not immediately apparent was the interoperability, operating system independence,
and hardware agnosticism that Java could provide as a middleware, or "glue," technology. As
processing power increased and software optimization continued, Java technology became the
cornerstone of a new breed of middleware—the application server. We are now witnessing the
peer-to-peer revolution that might lead Java back to the desktop.
Peer-to-peer offers a compelling model for the Java language. Its capability to run anywhere,
coupled with protocols that define peers of equals, provides unlimited potential for interoperability.
So now, as we witness the excitement over Web services, peer-to-peer computing, and envision
the Semantic Web, we are confronted with the future role of Jini.
As I stated at the start of this book, Jini has a role. It continues to evolve and adapt as a networkcentric solution supporting the new network reality of pervasive computing.
This final chapter, in some respects, presents where it all began for Jini. But now the maturity of
the technology, coupled with improved device processing capabilities, will play an important role
in changing middleware perception. Jini provides a new strand of intelligent middleware that
blends devices and applications into the fabric of the network.
Figure 14.1. Projections for Java have not always been on target, but have
somehow surpassed expectations.
320
Jini Environment and Assumptions
As previously illustrated, in order for a hardware or software component to join a Jini network, it
must satisfy several critical requirements:
•
•
•
It must be able to participate in the Jini discovery and join protocols.
It must be able to download and execute classes written in the Java programming
language.
It may need the capability to export classes written in the Java programming language.
The Jini environment assumes that these requirements can be met by all participating members. It
assumes objects or proxies for services can move and be downloaded by clients across the
network. It assumes the client is capable of downloading the necessary code in jar files. Even
though the overhead might not be significant in the client-PC market, the device market has
proven to be a significant challenge. Resource-constrained devices, that is, devices that have
limited memory and processing capacity, have challenged these Jini assumptions. Devices that are
not capable of supporting the Java 2 Standard Edition (J2SE) have been all but forgotten by many
commercial Jini efforts.
Figure 14.2. Jini assumes a client can download needed code.
321
Devices that are not connected directly to the network, unable to run a JVM, or are limited in
RAM, are actually representative of the current state of most devices. Yet, these devices can
provide important services, especially in support of work mobility. So if Jini is to play a role in the
new network reality, how will Jini adapt to the device market?
Can Jini Scale Down?
The challenges faced in this space include size (minimizing the JVM footprint), performance (low
battery power, limited-memory devices), developing drivers to provide functionality that falls
outside of the JVM sandbox, and the lack of useful tools relevant to embedded Java development.
Sun has known for some time that different development platforms are needed—one size does not
fit all. Today we have three distinct Java development environments directed towards different
processing requirements.
•
•
•
The Java 2 Enterprise Edition (J2EE) represents the high-end development environment
required to support scalable solutions in business-to-business (B2B) and electronic
commerce.
The Java 2 Standard Edition (J2SE) provides the middle ground solution to capture client
development directed at the traditional PC and workgroup environments.
The Java 2 Micro Edition (J2ME) now represents the attack on the device market,
specifically aimed at resource-constrained devices.
Figure 14.3. Sun has defined development environments to match the capabilities
of market segments.
322
Java 2 Micro Edition (J2ME)
J2ME is a more recent Java application environment. It is a framework for the deployment and use
of Java technology in the post-PC world. J2ME software is configured for a variety of market
segments.
The J2ME architecture is designed to be modular, using two fundamental elements: configurations
and profiles. Together, they deliver a specification for consumer electronics and embedded device
manufacturers to implement on their products.
Configurations and Profiles
A configuration is a virtual machine and minimal set of basic class libraries and APIs. It specifies
a generalized runtime environment for consumer electronic and embedded devices and acts as the
Java platform on the device.
A profile is an industry-defined specification of the Java APIs used by manufacturers and
developers to address specific types of devices. A profile is built on top of and utilizes the
underlying configuration necessary to provide a complete runtime environment for a specific kind
of device. Profiles specify both the APIs and the configuration. An application written to the
specification can execute in the specified Java technology environment without the addition of
other Java classes.
Figure 14.4. A configuration and profile provide the building blocks to J2ME.
323
Two configurations make up current J2ME technology:
•
•
Connected Limited Device Configuration (CLDC)
Connected Device Configuration (CDC)
Connected Limited Device Configuration (CLDC)
The Connected Limited Device Configuration (CLDC) was developed for devices with a small
amount of memory. It is used in small, resource-constrained devices, each with memory in the
range of 160–512KB. This provides the minimal functionality needed to minimize the memory
footprint for memory-constrained devices. These devices usually contain 16- or 32-bit processors
and a minimum total memory footprint of 128KB. The Mobile Information Device Profile (MIDP)
is used with CLDC. It is targeted at devices such as cell phones.
The CLDC is composed of the K virtual machine (KVM) and basic class libraries that can be used
on a variety of devices. The KVM is a portable virtual machine designed for small memory,
limited-resource, network-connected devices.
Figure 14.5. CLDC and MIDP targeting the "weak" device market.
324
Connected Device Configuration (CDC)
The Connected Device Configuration (CDC) was developed for devices with relatively large
amounts of memory: two megabytes or more of total memory available for the Java platform.
These devices typically require the full feature/functionality of the Java virtual machine. Target
devices for CDC contain connectivity to a network—often a wireless, intermittent connection,
with limited bandwidth (9600bps or less). Some devices that might be supported by CDC include
residential gateways, next-generation smart phones, two-way pagers, PDAs, home appliances,
point-of-sale terminals, and automobile navigation systems. Currently, CDC has one profile called
the Foundation Profile. The CDC is a superset of CLDC, and therefore a CLDC-compliant profile
is upwardly compatible with CDC.
The J2ME architecture is changing rapidly. To find the latest information you may want to visit
the Sun Microsystems site at http://java.sun.com/j2me/. Documentation and API specifications for
the CLDC and KVM releases can be downloaded from the site at
http://www.sun.com/software/communitysource/j2me/cldc/.
The CDC Class Library includes the following:
•
•
•
•
•
•
java.lang— VM system classes
java.util— Underlying Java utilities
java.net— UDP Datagram and File I/O
java.io— Java File I/O
java.text— Bare minimal support for I18n
java.security— Minimal security and encryption for object serialization
The Foundation Profile
The Foundation Profile is a set of Java APIs that, together with the CDC, provide a complete
J2ME application runtime environment targeted at consumer electronics and embedded devices.
The C Virtual Machine (CVM) in the CDC is the engine for the Foundation Profile libraries. The
CVM is a full-featured virtual machine designed for devices needing the functionality of the Java
2 virtual machine feature set, but with a smaller footprint.
325
In general, the Foundation Profile contains the complete basic Java packages from J2SE
technology, except that the GUI dependencies on java.awt are removed. The CVM supports
version 1.3 of the Java 2 Platform, and includes security, weak references, JNI, RMI, and JVMDI.
The basic Java packages from CDC are modified in the Foundation Profile by the following:
•
java.lang— Rounds out full java.lang.* J2SE package support for the Java
language.
•
•
•
java.util— Adds full zip support and other J2SE utilities.
java.net— Adds TCP/IP Socket and HTTP connections.
java.io— Rounds out full java.io.* J2SE package support for the Java language
•
•
java.text— Rounds out full java.text.* J2SE package support for I18n.
java.security— Adds Code Signing and Certificates.
input/output.
Figure 14.6. CDC and Foundation Profile targeting the "strong" device market.
Documentation and API specifications for the following releases can be downloaded from the Sun
Web site at java.sun.com/products.
•
•
•
The CDC release: java.sun.com/products/cdc
The CVM release: java.sun.com/products/cdc/cvm
The Foundation Profile release: java.sun.com/products/foundation
With the release of J2ME+CDC+Foundation Profile and the J2ME RMI profile, full-featured Jini
device support is possible.
Figure 14.7. J2ME/CDC provides a smaller footprint, yet retains the necessary
features to support Jini clients.
326
Does this mean devices unable to meet the requirements of the CDC and KVM are eliminated
from a Jini network? What about even smaller devices and support for CDLC?
The Jini community is betting on the Jini Surrogate Architecture.
The Jini Surrogate Architecture
The Surrogate Architecture defined by the Jini Community Process is also addressing resourceconstrained devices in a Jini network. It is providing the necessary classes to support very small
devices through the use of surrogate architecture. The concept is actually quite simple, and fits in
well with the Jini model of code movement and proxy delegation.
The surrogate architecture lowers the requirements that a device must meet to participate in a Jini
network. If a device has computational, resource, or network connectivity limitations, a surrogate
is provided. The surrogate performs work on behalf of the device, rather than requiring every
device to directly support code downloading and Jini capabilities.
The surrogate architecture also relies on mobile objects, but the direction of the motion is reversed.
Instead of loading mobile objects into a device, the device ships a mobile object out and into a
surrogate host. The mobile object moved out of the device runs within the surrogate host and
communicates with the device, if necessary, through private protocols.
Figure 14.8. The direction of code movement is what enables the Jini surrogate
architecture.
327
For example, a Palm Pilot or cell phone could house a small piece of code that gets exported to a
surrogate host. This code contains the necessary protocol and service information to communicate
with the device. This may be a jar file on the device or a pointer on the network where the jar file
can be found. How it does this initialization is dependent on the network protocol used to connect
the device. For example, there is a specification for how this works in IP networks. The surrogate
lives in a host capable of supporting the Jini environment. The surrogate appears to the Jini
network as the actual device; for instance, the cell phone appears to have registered with the Jini
network. However, the device does not actually participate in Jini discovery; rather it only needs a
mechanism to find its surrogate, such as listening on a specific TCP/IP port. Whenever another
service needs to interact with the device, it goes through the surrogate, which communicates back
to the device using whatever method has been implemented.
The Surrogate Host
Surrogate hosts provide the environment for surrogates to participate in a Jini network, thereby
providing device participation in a Jini network. Surrogate hosts manage the life cycle of
surrogates by loading, starting, stopping, and disposing of surrogates as necessary.
Figure 14.9. The surrogate host architecture enables very small devices to join a
Jini network through a surrogate. The surrogate host has the necessary resources
to support a JVM, RMI, and so on. The interconnect defines the protocol necessary
for a device to communicate with the surrogate host.
328
The surrogate host is responsible for implementing the interconnect protocol for a specific
interconnect. The first part of the interconnect protocol implements discovery. Discovery, in this
context, is the protocol that is used by the device and surrogate host to find each other.
The Surrogate Specification defines the following surrogate lifecycle states:
•
•
•
•
•
•
Device discovery—When a surrogate host discovers the presence of a device (or a device
discovers the presence of a surrogate host).
Surrogate retrieval—The action of locating the compiled class files of the surrogate and
loading them into the surrogate host. These class files are part of a jar file. The jar file can
be located within the device itself and transferred to the surrogate host directly from the
device, or it could be located elsewhere.
Activation—The act of instantiating the surrogate object, written in the Java
programming language, and then making the method call to begin the surrogate's
execution. Surrogate hosts can provide threads for these surrogates or whole JVMs. The
details of the execution environment are left to the surrogate host designer. However,
surrogates are assured some amount of isolation from each other, and isolation from other
elements of the surrogate host.
Execution—The steady state of a running surrogate. After they are executing, surrogates
can accomplish anything that other Java programs can do. They can, for example, access
other Jini services or provide Jini services themselves.
Deactivation—The act of stopping a surrogate.
Disposal—The act of disposing of the objects associated with the deactivated surrogate.
The Surrogate Interface
The Surrogate interface defines the activate and deactivate methods used by the
surrogate host to control the execution of the surrogate. This interface also provides the surrogate
access to its execution environment. The surrogate class must implement the Surrogate
interface.
The class implementing the Surrogate interface must be a public class, and must have a
public constructor that takes no parameters, so that a surrogate object can be created by
Class.newInstance.
Listing 14.1 The Surrogate Interface
329
package net.jini.surrogate;
public interface Surrogate {
void activate(HostContext hostContext, Object context) throws
Exception;
void deactivate();
}
The GetCodebase interface provides a method that enables the surrogate to set its codebase.
The GetCodebase interface is optionally implemented by the surrogate class. Implementing
this interface indicates that the surrogate will specify its codebase. This may be used when you
want the device to control the location of downloaded files. If the surrogate implements this
interface, the surrogate host invokes the getCodebase method and uses the returned URLs to
set the surrogate's codebase before the surrogate's activate method is invoked. This will
override the default codebase that is set by the -Djava.rmi.server.codebase property.
Listing 14.2 The GetCodebase Interface
package net.jini.surrogate;
public interface GetCodebase {
java.net.URL[] getCodebase(HostContext hostContext, Object context)
throws Exception;
}
The HostContext interface provides methods to access the execution environment provided
by the surrogate host. An object that implements HostContext is passed as a parameter to the
surrogate's activate method and the getCodebase method of GetCodebase.
Listing 14.3 The HostContext Interface
package net.jini.surrogate;
import net.jini.discovery.DiscoveryManagement;
public interface HostContext {
DiscoveryManagement getDiscoveryManager();
void cancelActivation();
SurrogateController newSurrogate(InputStream surrogate, Object
context,
DeactivationListener listener) throws
SurrogateCreationException;
}
The Madison Implementation
Sun Microsystems supplies a reference implementation of the surrogate host and an IP surrogate
that conform to both the Surrogate Architecture Specification and the IP Interconnect
Specification. They can be found on the www.jini.org site at
http://developer.jini.org/exchange/projects/surrogate/.
The Surrogate Specification is available at
http://developer.jini.org/exchange/projects/surrogate/specs.html.
The IP Project documentation is available at
http://developer.jini.org/exchange/projects/surrogate/IP/index.html.
330
The Surrogate Architecture provides an environment to support small devices in a Jini network.
Rather than requiring small devices to support the entire Jini environment, a surrogate is defined
that represents the device in the Jini network. Thus, the bar to device entry, in terms of capabilities,
has been lowered.
Will Devices Scale Up?
Lowering the processing requirements for devices to join a Jini network is only one facet of Jini
device support. Arguably, devices will increase in processing capabilities regardless of the
intended environment.
In 1965, Gordon Moore predicted that the number of transistors per integrated circuit would
double every 18 months. His forecast was that this trend would continue through 1975. What has
become known as Moore's Law has been maintained for far longer, and still holds true as we enter
the new century.
Figure 14.10. Moore's Law has demonstrated that processing power grows at a
predictable rate. It is important to put devices in a similar context.
Of course much of the talk about "weak" devices has to do with processing, memory, and power.
Yet it can be argued that Moore's Law also applies to the capabilities of devices.
For example, the Compaq iPaq Pocket PC supports the Intel StrongArm SA1110 microprocessor.
This processor already boasts 32MB of memory and 200+ MIPS at 1 watt. Savaje
(www.savaje.com), has a Java-based OS that supports the full J2SE on a StrongArm iPaq. We can
only imagine the capabilities of devices just two to three years out.
A strong case can be made for devices scaling-up to meet Jini requirements. Even if this is not the
case in the short term, the surrogate architecture provides another solution.
331
The Network Reality—Pervasive Computing
The new network reality is that devices, computers, automobiles, and even appliances are forming
a new "infosphere" that will interconnect us all (see Figure 14.11). Traditional network
management and administration will not keep pace with this change. Complementary technologies
such as Jini can build more intelligent services, reduce administration, and manage connectivity
complexity.
Figure 14.11. The new network reality—an "infosphere" of pervasive computing.
Interconnected devices have already changed our communication and information exchange
capabilities. The market for network-connected devices continues to grow. In fewer than five
years we have witnessed the Internet connecting the world, cell phones connecting individuals,
and devices connecting everything in between.
Summary
Interconnected devices are enabling a new generation of communication, information exchange,
and pervasive computing. Current network and application solutions should not underestimate the
interoperability impact.
The proliferation of connected devices will further exacerbate the integration and interoperability
dilemma. Solutions that boast best-of-breed integration capabilities will fail to keep pace with the
rapidly changing device market.
The Java language provides a platform for interoperability. The J2EE, J2SE, and now J2ME are
Java-based. The segmentation of these environments is based on business and technology profiles.
Jini provides a connection between these segments. Jini provides the common "thread."
The interconnected nature of our networks, coupled with connected devices, presents unlimited
potential. Harnessing this potential however, will require rethinking our basic assumptions about
networks, systems, devices, and software. All this points to solutions that are adaptable.
Jini is proving its adaptability. This book demonstrates Jini as a key enabler in P2P, Web service
integration, and now the new device market.
332
As explained in Chapter 1, "Introduction to Jini," the tipping point is the culmination of social,
economic, and technological changes that happen very quickly and sometimes from small events.
There are changes happening in networks that are going to cause technologies—some new, some
not so new—to soon hit the tipping point. Jini has the potential to be one of them.
333
Part IV: Appendixes
IN THIS PART
A Setting Up Your Environment
B Tools
C Links, Community, Resources
334
Appendix A. Setting Up Your Environment
IN THIS APPENDIX
•
•
What You Will Need
Gotchas and Common Pitfalls
This appendix will provide you with the information required to fully exercise the technology and
examples presented throughout the book. This includes references to the necessary software,
operating systems, hardware, and network technologies supported. In addition, common "gotchas"
and pitfalls will be highlighted.
What You Will Need
Jini is written entirely in Java. It uses many of the features that were introduced in the Java 2 SDK,
Standard Edition v 1.2 release, such as RMI remote object activation. At a minimum you will need
the JDK 1.2 release to run Jini, although I highly recommend the latest release. As of this writing,
that is the JDK 1.3 release or JDK 1.4 beta, and they are available at either
http://java.sun.com/j2se/1.3/ or http://java.sun.com/j2se/1.4/. The examples in this book were
tested using the JRE 1.3.1-b24.
Downloading Jini
You can download the Jini binaries and source from the Java Developer Connection at
http://developer.java.sun.com/developer/products/jini.
You must register to be a member of the Java Developer Connection. These downloads are
available after accepting the Sun Community Source License Agreement.
The Jini Technology Starter Kit v 1.1 was released 10/2000 and is the current release as of this
writing. The starter kit contains the Jini technology infrastructure implementations, as well as
supporting helper classes and services, including JavaSpaces. There is also a Jini Technology
Starter Kit v 1.2 Beta, released 9/2001 that is available.
In addition, there is a Jini TCK (Technology Core Platform Compatibility Kit v 1.1B) available;
however, the TCK is not covered in this book. The TCK consists of a set of tests that ensure that
Jini technology-enabled services and clients are compatible with the Jini specification.
The installation instructions are available at:
http://developer.java.sun.com/developer/products/jini/installation.index.html
You can download the Jini specifications from http://java.sun.com/products/jini/specs/.
System Requirements
Platforms on which the Jini Technology Starter Kit has been tested by Sun Microsystems include
the following:
335
•
•
•
Java 2 SDK, Standard Edition, v1.3.1 for Solaris SPARC(TM)/x86 on the latest FCS
versions of Solaris 2.6, Solaris 2.7, and Solaris 8 on Sparc and Solaris 8 on X86
Java 2 SDK, Standard Edition, v1.3.1 for Microsoft Windows 95/98/ME/2000/NT 4.0 on
Windows 2000 (2nd Edition) and NT 4.0 (SP6)
Java 2 SDK, Standard Edition, v1.3.1-b24 for Linux (Intel x86) on RedHat 7.1
Gotchas and Common Pitfalls
Jini requires an infrastructure and support environment properly configured and established to
work properly. Often this process can be difficult to developers new to Jini, especially if you are
also new to RMI and networking fundamentals.
If you are new to RMI, you should familiarize yourself with RMI fundamentals
(http://java.sun.com/products/jdk/rmi/) and ensure that your environment supports the simple
examples presented in Chapter 3. In addition, the "Remote Object Activation" tutorial is at
http://java.sun.com/j2se/1.3/docs/guide/rmi/activation.html and can assist you with learning RMI
fundamentals.
Network Configuration
Jini requires support for unicast TCP and multicast UDP. The former is used by subsystems using
Jini technology such as Java Remote Method Invocation (RMI). Both unicast TCP and multicast
UDP are used during the Jini discovery process. Chapter 4 contains a sample multicast sniffer that
can be used to display multicast packets being transmitted.
Firewalls present a major challenge to getting RMI, and therefore Jini functioning properly. To
address this issue, RMI uses two popular techniques to bypass common filtering: direct and
indirect forwarding using HTTP tunnels that encapsulate RMI and the Java Remote Method
Invocation Protocol (JRMP) in HTTP messages. If you have a firewall, you will need to determine
the technique used to filter messages (for example, port assignments), and then determine if
tunneling HTTP can provide a solution for your environment.
IP Address
A valid IP address is required to configure and enable Jini services. Invalid IP addresses are often
the result of a misconfigured host machine or lack of network support on your host machine.
Verify that the IP address is correct for your local machine. You can start with a simple ping
127.0.0.1 command to ping your localhost. Then try to ping your IP address and verify
return packets. Finally, try to ping the computer host name. If you have a problem with any of the
network packets not being replied to, then check the active routes in your network table. For
instance, type in the command netstat -nr. This should show you the IP addresses that are
set on your local machine.
Infrastructure
In this section the infrastructure is referred to as:
•
•
The HTTP server supporting downloadable code
The Remote Method Invocation Daemon (rmid) supporting activatable services
The HTTP Server
336
Jini requires a mechanism to move or download code across a network; for example, an HTTP
server. This mechanism does not have to be provided by the host itself, but the code must be made
available by some host on the network. There is a simple HTTP server that is provided with the
Jini distribution. The following list some potential problems:
•
•
•
•
Codebase—Probably one of the most frequently encountered problems in Jini has to deal
with setting and managing the codebase that clients and services use to download code
supplied by HTTP servers. The "class not found" problem often manifests itself when
changing environments because of an improper codebase setting. As mentioned earlier,
Jini requires that code be mobile. This involves setting a codebase property to point to an
HTTP server where downloaded code can be found. The code that is available for
download is annotated with a reference to its location, based on the codebase parameter.
localhost is undoubtedly NOT the correct location when you begin to expose your
clients and services on the network. Avoid the habit of using localhost in any of the
parameters required to start clients and services that provide downloaded code.
Port Conflict Error—The provided HTTP server will default to port 8080. If you receive a
port conflict error message, then check to ensure another server is not running on this port.
Unable to access jar files—The directory path to serve jar and class files is invalid. Check
-dir parameter and ensure that the jar and class files that you wish to provide to clients
are under that directory.
Service Starter not found—Can't find service launcher. The service launcher program
com.sun.jini.example.launcher.StartService is in the jiniexamples.jar file. Check to ensure your classpath is set to find the
StartService.class.
Remote Method Invocation Daemon (rmid)
The RMI daemon is crucial to deploying Jini. The RMI daemon typically runs in the background.
By default, when the daemon starts, it creates log files in a log directory under the directory where
rmid was started. These log files are where state information is saved for restarts. Under normal
circumstances, the log files are closed when rmid shuts down. If you encounter a problem with
service activation, you may have to manually remove the log files and delete the log directory. If
you encounter an activation exception, such as "logdir
c:\log_dir\tmp\reggie_log exists, specify a different path,"
then remove the directory and try to start the activation daemon clean. This is only to troubleshoot
a problem when a service may not have terminated properly.
You need to stop rmid to shut down the daemon properly. To shut down rmid, simply enter the
command rmid -stop.
If you receive "Activation.main: an exception occurred: Port
already in use: 1098," you need to kill the rmid process and then restart the daemon.
Security Manager and Policy File
You must set a security policy when running rmid. The problems most frequently encountered
have to do with not setting the security policy, or setting a security policy that is too restrictive.
In addition, all services and clients that are running in a Jini environment must have set a security
manager and defined an appropriate security policy file.
Summary
337
There are a number of resources available that can assist you with troubleshooting your Jini
environment. You should read the Jini Frequently Asked Questions at
http://www.sun.com/jini/faqs. Also, the Jini mailing-list archive at
http://archives.java.sun.com/archives/jini-users.html contains a large amount of information
regarding problems you might encounter. The archives allow you to search for a specific topic or
keyword. Search the archives using error messages, as search parameters often return a list of
discussion items applicable to your problem. Additionally, Appendix C of this book contains a
reference to popular Jini sites and resources.
338
Appendix B. Tools
IN THIS APPENDIX
•
•
•
Start-up Scripts
Building Jini
Service Browsing and Administration
There are a number of tools that you will find beneficial when learning Jini and JavaSpaces. This
is a rapidly changing area, so you may want to consult the Jini links referenced in Appendix C,
especially www.jini.org, to find the latest updates and releases.
Start-up Scripts
The source code and scripts explained in this book can be found at:
http://www.jworkplace.org
You can download the zip file of examples from that site. Included in the
jinijavaspaces.zip file are a number of service start-up scripts. The examples and
scripts can be found in the JiniJavaSpaces directory. Unzip the file to install and review
the examples presented:
>example-installdir/JiniJavaSpaces/services/scripts
The setup.cmd or setup.sh script should be modified to support your environment.
The JINI_HOME environment variable should point to your Jini distribution:
set JINI_HOME=d:\files\jini1_2
The HTTP_ADDRESS environment variable should point to your HTTP server hostname and
port assignment:
set HTTP_ADDRESS=hostname:8080
The HTTP_HOME environment variable should point to your HTTP directory path:
set HTTP_HOME=%JWORK_HOME%\services\lib
The JWORK_HOME environment variable should point to the book examples install directory:
set JWORK_HOME=d:\JiniJavaSpaces
Jini also comes with a GUI-supplied service activation program. It allows you to enter the required
and optional parameters in a tabbed pane to start Jini services as an alternative to scripts.
The following command starts the service launcher interface. From the command prompt, type in
the command:
339
java -cp %JINI_HOME%\lib\jini-examples.jar
com.sun.jini.example.launcher. StartService
To use the launcher, you can open and edit the properties file located under the
/jini1_1/examples/launcher directory from the file menu. This will generate the
tabs shown and allow you to customize the parameters to your environment. There is a properties
file for both Windows and Unix that supplies a list of default values.
After you load the appropriate properties file, your display should look similar to Figure B.1.
Figure B.1. The StartService Editor provides a graphical alternative to starting and
stopping services in the Jini community.
Building Jini
The build tool provides a graphical alternative to scripts and make files. You can compile and
create the necessary jar and rmic stub files using the build tool. The tool can be found at:
http://developer.jini.org/exchange/projects/outofbox/buildtool/
Once you have downloaded the build tool distribution, start the graphical tool by invoking
java -cp <jdk-installdir>/lib/tools.jar:<installdir>:
</buildtool1_0/lib/buildtool.jar
com.sun.jini.tool.build.BuildTool -gui
You will see a display similar to Figure B.2.
340
Figure B.2. The build tool is being developed to provide a graphical environment
for building Jini and user-defined clients and services.
Service Browsing and Administration
The startBrowser.cmd and startBrowser.sh files for Windows and Unix are used
to start the lookup browser.
@echo off
@call setup.cmd
set GROUPS=public
echo Jini and JavaSpaces Application Development
echo ------------------------------------------echo Jini install directory
%JINI_HOME%
echo Examples install directory
%JWORK_HOME%
echo Web server
%HTTP_ADDRESS%
echo Default group
%GROUPS%
echo ------------------------------------------echo Starting the Service Browser
java -cp %JINI_HOME%\lib\jini-examples.jar
-Djava.security.policy=%JINI_HOME%\example\browser\policy
-Djava.rmi.server.codebase=http://%HTTP_ADDRESS%/jini-examples-dl.jar
com.sun.jini.example.browser.Browser –admin
The lookup browser can be used to view your LUS environment. You can display the services that
are currently registered at each LUS that is active (see Figure B.3).
341
Figure B.3. The lookup browser is used to display active services in your Jini
community.
Administration of services is possible by highlighting the service and right-clicking on the service
item as shown in the figure. Selecting "Service Admin" displays the administration panel, as in
Figure B.4. You can change groups, locators, and remove the service from the LUS and rmid.
Figure B.4. The ServiceItem Editor assists in the administration of services in your
Jini community.
342
Summary
Jini currently lacks a robust set of development and deployment tools. The tools developed are
primarily from the Jini community and not from tool vendors. Whether this has slowed the
adoption rate of Jini, or the slow rate of adoption has limited tool development is a catch-22.
Nonetheless, this is an area that requires attention to improve Jini development productivity.
The Jini community site at www.jini.org/projects.html contains projects dedicated to improving
the Jini development environment.
The Out of Box Project is focused on improving the Jini installation and deployment experience.
Information on the Out of Box project is available at the following sites:
http://developer.jini.org/exchange/projects/outofbox/
http://developer.jini.org/exchange/projects/outofbox/tools/index.html
http://developer.jini.org/exchange/projects/outofbox/buildtool/buildtool.html
The RIO Project has been working on generic development tools. Information on the RIO project
is available at:
http://developer.jini.org/exchange/projects/rio/
The Jini distribution includes a utility class used to inspect a set of classes and determine class
dependencies. You can invoke the ClassDep utility from the command line. See the
ClassDep.java file for documentation.
java -classpath
d:\files\jini1_1\lib\tools.jar;d:\jdk1.3\lib\tools.jar com.sun.
jini.tool.ClassDep
343
Appendix C. Links, Community, Resources
IN THIS APPENDIX
•
•
•
•
Links
Specifications
Community
Resources
Links
The Jini distribution and supporting software can be found at the following Web site:
http://developer.java.sun.com/developer/products/jini
The JDK releases for Jini are available at this Web site:
http://java.sun.com/j2se/
The Remote Method Invocation (RMI) can be found at:
http://java.sun.com/products/jdk/rmi/
Specifications
All the Jini specifications are contained on the Sun Microsystems site at
http://www.sun.com/jini/specs/. The specifications include the following:
•
•
•
•
•
•
•
•
•
•
•
Jini Technology Core Platform Specification
Jini Discovery Utilities Specification
Jini Entry Utilities Specification
Jini Lease Utilities Specification
Jini Join Utilities Specification
Jini Service Discovery Utilities Specification
Jini Lookup Discovery Service
Jini Lookup Attribute Schema Specification
Jini Lease Renewal Service Specification
Jini Event Mailbox Service Specification
JavaSpaces Service Specification
The Remote Method Invocation Specification can be found at
http://java.sun.com/products/jdk/1.2/docs/guide/rmi/spec/rmiTOC.doc.html.
Community
344
The Jini community central site with access to all Jini projects registered by community members
can be found at the following address:
http://www.jini.org
Jini participation in project discussion, development, and sharing can be found at:
http://www.jini.org/participate.html
Resources
The Jini community has produced many Web-accessible resources to assist in your Jini
development and experience. You might want to start here:
http://www.jini.org/resources/
The Jini FAQ site:
http://www.sun.com/jini/faqs
4The Jini mailing list archives: http://archives.java.sun.com/archives/jini-users.html
The JavaSpaces mailing list archives:
http://archives.java.sun.com/archives/jini-users.html
Another resource you might find valuable is a popular site for articles and resources pertaining to
Jini, maintained by Bill Venners of Artima.com:
http://www.artima.com/jini/
Finally, all of the examples and updated errata from this book can be found at:
http://www.jworkplace.org
345