Professional Documents
Culture Documents
Poetzsch-Heffter CBSD-Textbook 2009 PDF
Poetzsch-Heffter CBSD-Textbook 2009 PDF
Poetzsch-Heffter CBSD-Textbook 2009 PDF
Embedded Systems
Textbook e-m.4
Component-Based
Software Development
Author
Prof. Dr. Arnd Poetzsch-Heffter
This work is protected by copyright. All rights thus conferred, particularly the rights to copy and
disseminate as well as the rights to translate and reprint the entire work or parts thereof, are reserved. Beyond the permissions regulated by copyright, no part of this work may be reproduced
in any way (print, photocopy, microfilm, or any other process) nor processed, copied, or disseminated using electronic systems without the written permission of the University of Kaiserslautern.
Table of Contents
Table of Contents
Glossary
ix
References
xi
xv
1
1.1
1.2
1.3
1.4
1.5
1.6
1
1
2
4
10
11
12
2
2.1
2.2
2.3
2.3.1
2.3.2
2.3.3
2.3.4
2.4
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.5
2.6
Component Software
Learning objectives of this chapter
Software systems and component software
Software components
Component specification
Component implementation
Component composition
Deployment
Component models
Component behavior and communication
MTC: Multi-threaded components
ACC: Asynchronously communicating components
SCC: Synchronously communicating components
CSC: Clock-synchronous components
Component infrastructure and framework support
Problems for Chapter 2
15
16
16
20
22
29
31
35
37
37
39
40
43
46
48
50
3
3.1
3.2
3.3
3.3.1
3.3.2
3.3.3
3.3.4
3.3.5
3.4
3.4.1
3.4.2
3.5
53
54
54
56
56
57
58
58
60
60
61
65
68
ii
Table of Contents
3.6
68
4
4.1
4.2
4.2.1
4.2.2
4.2.3
71
71
72
72
74
4.2.4
4.3
4.3.1
4.3.3
4.3.3
4.3.4
4.4
4.5
5
5.1
5.2
5.3
75
76
83
84
91
96
98
99
101
5.4
5.4.1
5.4.2
5.4.3
5.4.4
5.5
103
103
103
108
109
110
113
119
122
124
6
6.1
6.2
6.3
6.3.1
6.3.2
6.3.3
6.3.4
6.3.5
6.3.6
6.3.7
6.4
6.4.1
6.4.2
6.5
127
127
128
129
129
132
136
139
140
143
145
147
148
149
151
iii
Table of Contents
7
7.1
7.2
7.3
7.3.1
161
161
163
165
Index
175
iv
Table of Contents
Glossary
Glossary
Active component: An active component has at least one thread running in the
component that is the source of communication with the environment.
Adaptation: Component adaptation means modifying functional, non-functional,
or technical properties of a component such that the adapted component fits the
needs of the composition context.
AUTOSAR: AUTOSAR is a joint initiative of a number of major players in the
car industry to develop an AUtomotive Open System ARchitecture and to overcome the current situation characterized by proprietary solutions that hinder the
exchange of components and applications among automotive OEMs and suppliers.
Behavior: By the behavior of a component, we mean the possible communications a component can have with its environment. The behavior of a passive component can be described by the reactions to methods called on the component or
the reactions to messages sent to the component. An active component can communicate with the environment without a prior incoming call or message.
Callback: A callback scenario occurs when a component CP1 calls a method m
implemented by a component CP2 and when the execution of m leads to a call to
a method of CP1.
Common Object Request Broker Architecture: See CORBA
Component: See software component and program component.
Component-based software development: Component-based software development is a sub-discipline of software engineering. Its goal is to provide concepts,
methods, languages, techniques, frameworks, tools, standards, and organizational
infrastructure for the development, provision, and selection of reusable software
components as well as for the development of component-based software systems
(in the sense below) using these components.
Component-based software system: A component-based software system is a
software system whose implementation is composed of software components according to the definition given below. Furthermore, we require that the component
structure refines the (described) architecture of the system.
Component container: The container of a component framework provides the
functionality for managing components. The container is executed in its deployed,
i.e., a component C is deployed in a container D. The container handles registration and connection of components. Furthermore, it makes general services available. The container is part of the component infrastructure and independent of the
developed component-based systems.
vi
Glossary
Glossary
the descriptors of EJB defining further properties like access rights, transactional
behavior, and persistence management.
Development stages: Software components and systems can be in different stages
of development. They can be designed, programmed, configured, deployed/ installed, and executing.
Embedded system: An embedded system is a computer system designed to perform one or several dedicated functions, often with real-time computing constraints. It is embedded into a complete device, often including computer hardware and mechanical parts.
Enterprise JavaBeans (EJB): The Enterprise JavaBeans framework supports the
development of component-based, distributed, transactional business applications
and their deployment within application servers. Components within the framework are called Enterprise JavaBeans or simply EJBs or beans. The EJB framework specification was created under the Java Community Process to provide
public participation in its definition and development.
Glue code: Glue code is code written in a scripting or programming language that
performs the composition of component instances either locally in one process or
remotely by using communication mechanisms.
Environment: Software components and systems never operate in isolation. They
interact with human users or other technical systems either via software or by directly using sensors and actors. The interaction includes user inputs, outputs for
users, messages, sensor values or calls from system parts in the environment, as
well as messages, output, or calls to such parts. We speak of the environment of a
software component or system to refer to the outside world it interacts with.
Implementation: Implementation mainly consists of the development of the programs realizing system components. It can include building aspects and preparation of deployment.
Interface: The term interface is overloaded. The interface of an object or port describes the methods or messages that can be used to communicate with the object
or port. The interface of a component comprises the interfaces it provides at its
ports and the interfaces it requires from other components.
Interface definition language (IDL): Interface definition languages allow defining the methods and types of component interfaces in a way that is independent of
the programming language. An example of such a language is the CORBA IDL.
OEM: OEM is the abbreviation for original equipment manufacturer.
OSGi: OSGi is a component framework for Java. OSGi originally stood for Open
Service Gateway initiative. Currently, it is marketed under the header OSGi
The dynamic module system for Java (see http://www.osgi.org). The OSGi
framework is supported by a large non-profit industry alliance.
vii
viii
Glossary
Passive component: A passive component only reacts to method calls and incoming messages. It has no thread of its own that initiates communication.
Persistence: Persistence means storing and maintaining data between different
runs of one or several systems. In many domains and areas, component-based applications have to manage persistent data. Component frameworks support this
important task by connecting the data state of components to relational databases.
Platform: The platform for a software system or component is the software/hardware system on which it is deployed. The platform provides the basic
functionality and resources. For component systems, it typically includes the
computer hardware, the operating system, middleware, and potentially a component container and framework.
Port: The access points for communicating with components are called ports.
Program component: A program component is a part of a program written in
some programming language, such as a procedure or a program module. Program
components can be used as implementations for software components, but not
every program component satisfies the properties we require for software components.
Remote method invocation (RMI): Remote method invocation (RMI) allows
calling methods on references of objects outside the local process. RMI is more
general than a remote procedure call, because the caller does not have to know
where the receiver object is.
Software component: A software component is a part of a software system or an
entity used for composing software systems. More precisely: Software components have well-defined interfaces and a behavior that can be described independent of the contexts in which they are used. Software components have an implementation that can be independently composed and deployed. To be composable,
software components follow a component model that determines the common
computation, communication, and composition principles and rules underlying a
class of components.
Stages: see development stages
ix
References
References
[1]
[2]
Rausch, A.; Reussner R.; Mirandola R.; Plasil, F. (Eds.): The common component modeling example: Comparing software component models. LNCS
5153, Springer-Verlag, 2008.
[3]
Szyperski, C. with Gruntz, D; Murer, S.: Component software: Beyond object-oriented programming. Second Edition, Addison-Wesley, 2002.
[4]
[5]
[6]
[7]
Armstrong, J.; Virding, R.; Wikstrm, C.; Williams, M.: Concurrent programming in Erlang. Second Edition. Prentice Hall, 1996.
[8]
Armstrong, R.; Kumfert, G.; McInnes, L. C.; Parker, S.,; Allan, B.; Sottile,
M.; Epperly, T.; Dahlgren, T.: The CCA component model for highperformance scientific computing. Concurrency and Computation: Practice
and Experience, 18(2):215-229, 2006.
[9]
Bass, L.; Clements, P.; Kazman, R.: Software architecture in practice. Addison-Wesley, 1998.
[10] Becker, S.; Koziolek, H.; Reussner, R.: The Palladio component model for
model-driven performance prediction. Journal of Systems and Software,
82(1):3-22, 2009.
[11] Benton, N.; Cardelli, L.; Fournet, C.: Modern concurrency abstractions for
C#. In B. Magnusson (ed.): Proc. ECOOP 2002, LNCS 2374, pp. 415-440.
Springer-Verlag, 2002.
[12] Berry, G.: The foundations of Esterel. In: Proof, language, and interaction:
essays in honour of Robin Milner, pp. 425454. MIT Press, 2000.
[13] Binnema, D.-J.: Bonobo components: Architecture and application (available online).
[14] Bolton, F.: Pure CORBA. Sams, 2001.
[15] Bruneton, E.; Coupaye, T.; Leclercq, M.; Quma, V.; Stefani, J.-B.: The
Fractal component model and its support in Java. Software practice and ex-
xi
xii
References
perience (special issue on experiences with auto-adaptive and reconfigurable systems), 36(11-12):1257 - 1284, 2006.
[16] Buschmann, F.; Meunier, R.; Rohnert, H.; Sommerlad, P.; Stal, M.: Patternoriented software architecture: A system of patterns. John Wiley, 1996.
[17] D'Souza, D. F.; Wills, A. C.: Objects, components, and frameworks with
UML. The Catalysis Approach. Addison-Wesley, 1999.
[18] Gao, J.; Tsao, H.-S. J.; Wu, Y.: Testing and quality assurance for component-based software. Artech House, 2003.
[19] Goetz, B. with Peilerls, T; Bloch, J.; Bowbeer, J.; Holmes, D; Lea, D.: Java
concurrency in practice. Addison-Wesley, 2006.
[20] Griffel, F.: Componentware: Konzepte und Techniken eines Softwareparadigmas. dpunkt.verlag, 1998.
[21] Heinecke, H.; Damm, W.; Josko, B.; Metzner, A.; Kopetz, H.; Singionvanni-Vincentelli, A.; Di Natale, M.: Software components for reliable automotive systems. In: Proc. of the conference on design, automation, and test in
Europe. ACM, 2008.
[22] Heineman, G. T.: Councill, W. T.: Component-based software engineering:
Putting the pieces together. Addison-Wesley, 2001.
[23] Hissam, S.; Ivers, J.; Plakosh, D.; Wallnau, K. C.: Pin component technology (V1.0) and its C interface. Technical Note CMU/SEI-2005-TN-001,
2005 (available online).
[24] Hoare, C. A. R.: Communicating Sequential Processes. Prentice Hall International, 1985. (freely available online)
[25] Hofmeister, C.; Nord, R.; Soni, D.: Applied software architecture. AddisonWesley, 2000.
[26] Johnson, R.: Expert one-on-one: J2EE design and development. Wrox
Press, 2002.
[27] Kiczales, G.; Lamping, J.; Mendhekar, A.; Maeda, C.; Lopes, C.; Loingtier,
J.-M.; Irwin, J: Aspect-oriented programming. In: Proc. of the European
Conference on Object-Oriented Programming, LNCS 1241, pp. 220242.
Springer-Verlag, 1997.
[28] Kruchten, P: The 4+1 view model of architecture. IEEE Software, 12(6):4250.
[29] McHale, C. J.: Corba explained simply. 2007 (self-published, available
online).
References
xiii
xiv
References
xv
xvi
1.1
have obtained a good idea of what a software component is and what it means
to build software systems from components,
know the most important relationships between software engineering in general and CBSD,
have a clue as to how to work with this textbook.
1.2
Goals of CBSD
software development, the later goals are mainly important for economical and
management reasons. In the following, we will explain the goals and then shortly
discuss their relationships.
Quality. Component-based software development extends and refines the development principle of low coupling and high cohesion: Structure the system in
such a way that it consists of loosely coupled components that encapsulate highly
interdependent aspects. Well-defined interfaces and independent component behavior help to improve the quality of system designs and implementations.
Flexibility and evolvability. Software systems have to be adapted to new requirements. Component-based development allows maintaining and evolving the
system by exchanging components incrementally. The well-defined interfaces and
independent component behavior make clear which parts of a system are possibly
affected by changes. As we will see later in this course, component technology
allows adding or exchanging components without recompilation of the system and
support modification at runtime. These features increase flexibility during maintenance and evolution.
Separate development. Building systems from components with well-defined
interfaces and independently described behavior allows distributing the development tasks to different groups or even different companies. Provided that the overall design was correct and that the development groups respect the interfaces
and implement the required behavior, system composition and integration should
cause no problems. In case composition fails, component-based design helps to
assign blame to the groups that caused the errors. Separate development is further
simplified if the component-based design is built on standards, is able use common services, and is supported by a technology for composition.
Role separation. Role separation means that the process of developing, deploying, administering, and using a software system can be clearly separated into different roles. Component frameworks make it possible to separate the responsibility of developing and managing a software system into five roles: component provider, framework provider, application provider (constructs an application from a
number of components), application deployer (installs an application on a platform), and system administrator. Role separation is important for division of labor, specialization, improved tooling, and better competition in the software market.
Reuse. Reuse on the level of plain program code is certainly helpful for the development of a new system. However, the program code and its effects have to be
understood in detail and reuse can only be done if the reused code and the new
system are developed in the same programming language. Reusing software components is a compelling idea that overcomes restrictions. Based on the interfaces,
component frameworks enable deployment and composition of components implemented in different languages. Furthermore, having standardized component
descriptions simplifies the search for components providing the needed features.
Time to market. If CBSD can achieve the above goals, the development time for
a system can be reduced considerably. This can provide a competitive edge to a
software producer. And it would allow society as a whole to better exploit the potential of good software products.
Market of software components. Whereas competition on the software market is
currently mainly about complete products, a more fine-grained market of components would allow more small and mid-size companies to participate in the market. The expected results are better and cheaper software components, less dependence on quasi-monopolists, and faster technological development in the software industry and related industries as a whole.
Discussion. It should be clear that these motivations are not independent. For example, if reuse allows incorporating existing components into new products, time
to market can be reduced. What is less clear is that there is a mutual dependency
between the more technically and the more economically oriented goals. On the
one hand, component technologies are needed to enable relevant component reuse
and establish a component market. On the other hand, component technologies
will only be successful if the additional costs and care it takes to develop reusable
components is justified by the gains of CBSD.
1.3
Figure 1.1:
Logical
components of the
retail store system
Analysis. To describe and analyze the behavioral requirements of a complex system, it is helpful to structure it into parts. As the behavior is usually related to the
logical view of a system (see Subsection 3.4.1), we call these parts the logical
components. The logical components are defined in such a way that it becomes as
easy as possible to state the system behavior. In our example, we used a hierarchical structuring (see Figure 1.1): The retail store system consists of a store management component and one or more cash desks. The store management consists
of a database and components for ordering new items, for booking the payments
and sold items, and for providing user access to the database. Each cash desk consists of three controllers for the input/output devices and a management component.
Based on the logical components, we can formulate the behavioral requirements.
For the example, this would especially mean to define
the possible kinds of behavior of the cash desk management: How are the interactions with the controllers? Should it be possible to take an already
scanned item out of a payment transaction? When is the booking done? What
happens if the printer fails?
the user interactions supported by the user access component: Does the user
trigger the ordering? What kinds of statistics can be produced by the system?
What should become clear here is that the logical components are primarily a
means to provide a first structure of the system that allows stating and analyzing
the behavioral requirements. During design and implementation, the logical components and their structure can be fully or largely realized by corresponding software components. This has the advantage that the description of the logical components can be reused in describing the software components. However, as we
will see, during design other constraints and aspects have to be taken into account.
And this might lead to a different breakdown into software components, which
does not match the structure of the logical components.
Design. The essential outcome of the design phase is the architecture of the
software system. The architecture describes the software components used to implement the system, their distribution to platforms, and their composition. The
challenge for design is that architectures have to meet constraints from quite different areas and a decision in one area often affects options in other areas. In particular, the following areas and decisions are pertinent:
1. Design of the structure of the software components realizing the functional
behavior of the system together with the communication techniques used.
2. Specification of the software components, that is, specification of their interfaces and behavior (possibly including their life cycle at runtime).
3. Specification of the schemata and formats of the data that is interchanged between components.
4. Selection of available software components (e.g., domain-specific components, database management systems, infrastructure components).
5. Configuration and adaptation of selected software components.
6. Selection of the implementation languages, frameworks, and development and
maintenance tools.
7. Selection of the physical and virtual platforms (e.g., computers and their operating systems, devices and their controllers) and their connections.
It is important to understand the often tight relationships between these design decisions. Here are two examples of such relationships in the context of the design
of the retail store system. The decision to get a bar code scanner from a certain
vendor V might affect the decision about how the cash desk management component is implemented, e.g., because the scanner controller of vendor V can only be
used together with a certain operating system and implementation language. A
decision about the distribution of the software components to the underlying computer platforms might affect the selection of the needed hardware. For example, if
we deploy the cash desk management components for all cash desks on a central
server, we need less computation power at each cash desk, which may result in
less hardware and maintenance costs.
To illustrate some possible design decisions, we shortly discuss a high-level architecture of the retail store system (see Figure 1.2). Unlike in the logical view,
where boxes represent logical components, the boxes in Figure 1.2 represent
software components, that is, we assume not only that they have well-defined
interfaces and described behavior, but that the system implementation realizes
these interfaces and the component behavior. By saying that the implementation realizes the components, we mean that
each component of the architecture corresponds to a module or part of the
program code,
the program code provides the interfaces of the component and implements
the behavior,
the program code is deployable independently.
Furthermore, the architecture as sketched by Figure 1.2 indicates the decision regarding the physical distribution of the system. There is one StoreServer (gray)
and one CashDeskPlatform (yellow) for each cash desk.
Figure 1.2:
Architectural
components of the
retail store system
The structure of the architecture is slightly different from the logical view, reflecting design decisions already made before. We decided in favor of a specific cash
desk computer with integrated printer and cash box. The cash desk platform
(computer and operating system) comes with a simple CashDeskControl, which is
not sufficient for our purposes. The operating system allows installing additional
programs, but programming for this platform is expensive. Thus, we decided to
place the CashDeskManagement on the StoreServer. For cost reasons, the scanner
of another vendor should be used. That is why we have to develop a ScannerAdapter, which allows to integrate the scanner with the CashDeskControl. On
the server side, we decided to use a component framework for application servers
with integrated database management system. In addition to the database schema,
we have to develop the ordering and booking component and the services and user
interfaces for user access. All software components that need development are
depicted with a bold line in Figure 1.2.
The details of the sketched architecture are not important for this introduction.
However, it is important to understand the differences between the logical view of
Figure 1.1 and the architecture in Figure 1.2. Whereas in the former diagram, the
components and their structure simply meant some abstractions used to explain
the overall system behavior, the components and their structure in the latter diagram reflect the implementation of the system. That is, each component in the architecture corresponds to a part of the program code realizing this component (in
the above sense). Putting it the other way round, a component of the architecture
is an abstraction of some part of the program code. This correspondence is a characterizing feature of software components.
Without going into details here, the simple example illustrates some typical aspects of CBSD:
There is an interplay between platform, languages, and tooling on the one side
and definition of components and their structure on the other side (for example, the selection of the CashDeskPlatform influenced the implementation of
the cash desk management).
Components from different vendors usually have to be adapted (e.g., a scanner
adapter was needed). Adaptation can be caused by interfaces and behavior that
do not match, but also by different implementation techniques.
The composition of components can take very different forms (in the example,
we could have the following forms: the ScannerAdapter is statically linked to
the CashDeskControl; the CashDeskControl communicates with the CashDeskManagement via sockets; the connections between ordering, booking,
and database are provided by the component framework used).
In summary, software components are independently describable entities of their
own that link the architecture to the implementation. Considered in a bottom-up
process, we can use existing software components, adapt some of them, and compose a system out of them. Considered in a top-down process, we can develop an
architecture from the requirements of the system, define the interfaces and behavior of the components, implement them separately, and finally compose them.
CBSD does not favor bottom up over top down or the other way round. The crucial thing is an architectural design based on well-defined components that can be
tracked down to the implementation.
Implementation. Implementation essentially means developing the programs that
realize components. It may include building and deployment aspects. Software
components are mediators between design and implementation. On the one hand,
they are more abstract than programs, because we require them to have an implementation-independent specification of interfaces and behavior. In particular, a
component specification can be implemented in different ways and in different
languages. On the other hand, we require software components to meet implementation-related requirements:
10
1.4
1.5
CBSD is not a mathematical theory that can be presented based on strict definitions. Similar to a paradigm, it is a more or less consistent (semantic) web of notions, concepts, techniques, language aspects, methods, and artifacts. Such a web
11
12
is difficult to convey using linear text. Although we have tried hard to linearize
the information in such a way that each chapter only builds on its predecessors,
we recommend rereading Chapters 2 and 3 after having studied some of the later
chapters. The reason is that a number of issues treated in the earlier chapters are
motivated by the needs of the later chapters about component frameworks. Furthermore, we recommend consciously using the chapter and section structure of
this book as a skeleton for the explained concepts.
The rationale of the structure of this textbook is as follows. This introduction sets
the scene by explaining the goals of CBSD, building up some intuition of what
software components are, and relating CBSD to other software engineering aspects. Chapter 2 presents the fundamental notions of software systems, components, composition, and frameworks. Based on these notions, Chapter 3 explains
the development of component-based software systems and describes the relationship of CBSD to adjacent development techniques and other relevant engineering
aspects. Chapter 4 is about basic component frameworks and their features (using
OSGi as an example). Chapter 5 presents component frameworks for heterogeneous and distributed systems (using CORBA as an example). Chapter 6 deals with
component frameworks for the construction of application servers (using Enterprise Java Beans, EJB for short, as an example). Chapter 7 gives a short overview
of component technology for embedded systems. Finally, Chapter 8 concludes.
At the end of each chapter, you will find a number of self-review questions. You
should be able to answer all of them. If you are not sure about the answer, you
should go back to the corresponding text passage (the appendix provides help for
answering the questions).
Prerequisites. We assume that you, the reader, have experiences with the design
and implementation of software. You should know the basics about finite automata and should be able to write Java programs and read programs in similar languages.
1.6
Problem 1.1:
Motivations for
CBSD
Problem 1.1:
Problem 1.2:
Logical and
software
components
Problem 1.2:
What are the main motivations for CBSD? Name the corresponding stakeholders
for each motivation!
What is the difference between the logical components and the software components of a system? What are reasons why the two component structures may deviate?
13
Problem 1.3:
What is the difference between a software component and a program component
according to the explanations of this chapter? What do we expect from a software
component that is missing if we only have a program module?
Problem 1.4:
Name the three different views of software components mentioned in the text!
Problem 1.3:
Software and
program
component
Problem 1.4:
Three views of
software
components
14
Component Software
Component software is built from software components. To be composable, software components have to adhere to a component model describing what components look like and how they are composed. In general, components following different component models cannot be composed. Component models can differ in
many aspects:
What are component interfaces and how are they described?
What is the behavior of components and how is it specified?
What is the execution and communication semantics of components?
What kind of data do they support?
What composition mechanisms are provided?
Which implementation technology is used?
Furthermore, component models are designed for a variety of application scenarios and different goals and are based on different assumptions about the hardware
and software platform.
In this chapter, we discuss the basic notions and problems that come up when we
want to build a software system from components. The discussion aims to substantiate the explanation of software components given in Section 1.2:
A software component is a part of a software system or an entity for composing
software systems. More precisely:
Software components have well-defined interfaces and a behavior that can
be described independent of the contexts in which they are used.
Software components have an implementation that can be independently
composed and deployed.
To be composable, software components follow a component model that determines the common computation, communication, and composition principles and rules underlying a class of components.
This explanation follows the definition given in [3], which is the most frequently
cited book on component software and which deals with concepts, frameworks,
and technology. [22] provides a large collection of articles also covering software
engineering aspects. [20] relates CBSD to traditional software development. A
book that is more implementation-oriented and covers implementation-related aspects of component frameworks is [4].
15
16
After presenting the learning objectives, this chapter looks at software systems
and their relationship to components (Section 2.2). As software systems are the
construction goal of CBSD, it is important to understand their basic aspects. Then,
we explain central notions of software components (Section 2.3). In particular, we
describe aspects such as component specification, implementation, and deployment. Section 2.4 presents different component models and communication mechanisms. Finally, we consider component infrastructures and central aspects of
component frameworks (Section 2.5). That is, we focus on the technical aspects of
component software in this part of the textbook. Development and engineering
aspects will be treated in Chapter 3.
2.1
2.2
What is a software system? This question has several answers. Answers can, for
example, explain a system in terms of the program code. Or, they can consider
systems at runtime. In order to get a more precise understanding of software systems and their basic structures, we first consider installed or running systems. This
allows us to uniquely identify them, similar to physical objects. For example, we
can speak of the GNU Chess installation in certain files on a given computer as
being distinct from another installation on a different (or even the same) computer; or we can speak of the World Wide Web as a system installed on thousands
of computers. Similarly, we can identify running software systems by the corresponding processes and the system installation they are executing. Thus, like most
collections of physical objects, installed or running software systems have
an identity (in particular, we can say where the system parts are installed or
running);
a lifetime (from installation to uninstallation);
17
a state at each point in time (the state of a software system tells us what parts
of the system are running and comprises information about variables and the
state of execution);
a behavior.
In order to investigate and describe a system, we need to agree on the interfaces
separating the system from the context it is embedded in. As shown in Figure 2.1,
we distinguish two kinds of system interfaces: the interface between the system
and its platform (dashed line) and the interface between the system and its environment (solid line). The platform and environment of a software system have
different characteristics. That is why they play different roles in overall descriptions of systems.
Figure 2.1:
The two principle
interfaces of a
software system
18
tem is usually closely linked to its platform, there are good reasons to make the
distinction between a software system and its platform:
A platform may be used for several different software systems. Thus, it is reasonable to describe the platform separately and reuse it.
Separating the platform and the software system of interest allows focusing on
the relevant system parts.
During runtime, the user only interacts with the system and need not to be aware
of an underlying platform. There is one typical exception: In the case of terminating systems, the user usually starts the system by interacting with the platform.
System environment. A software system never operates in isolation. Typically, it
interacts with human users or other technical systems either via software or by
directly using sensors and actors. In particular, a software system can be a subsystem of a larger system. The interaction includes user inputs (keystrokes, mouse
clicks, text input, etc.), outputs for users (sound, text, paper, graphics, video, etc.),
messages, sensor values or calls from system parts in the environment, as well as
messages, output, or calls to such parts. As it is conceptually irrelevant whether a
system interacts with human beings or technical systems, we simply speak of the
environment of a software system to refer to the outside world it interacts with.
Whereas the communication of a software system with its platform is only needed
to understand its implementation, the interaction with the environment is crucial
for understanding the behavior of a software system. For example, in order to understand the behavior of a chess game with a graphical user interface, we have to
learn how to move the pieces and how to interpret the output from the system, but
we do not have to know how it allocates memory or how it uses the window too
kit.
Software systems. In order to provide a focused explanation, we spoke only of
single copies of installed or running software systems. It should be clear that
software systems are more general artifacts. In particular, they can be installed on
many different computers/platforms. As long as these platforms have the same
properties, the difference between designing a system for one installation or for
several installations is small. However, one often wants to design systems that can
run on different platforms. In these cases, the platform becomes a parameter of the
system. This parameter is instantiated at configuration or deployment time.
In practice, software systems have a number of implicit or explicit parameters.
The platform is just one example. Systems can be parameterized according to
their functional properties (e.g., an installation of a text processing system might
or might not include a spell checker). They may vary with respect to the size of
data or the number of transactions they can handle (e.g., database management
systems configured for a few gigabytes or hundreds of terabytes). They can be
configured for different system environments and users.
In summary, a software system is a technical system or subsystem in which software plays a dominant role. It should have a clear interface to its platform and to
the system environment. It may be parameterized for installation on different platforms, for providing different functionality, and for adaptation to different users
and environments. In particular, a software system has different stages. It can be:
1. designed, i.e., design specification of the system is completed;
2. programmed, i.e., the program code for the possibly parametric system is finished;
3. configured, i.e., prepared for deployment/installation;
4. deployed/installed, i.e., stored on a platform and ready for execution;
5. executing, usually in the form of processes on one or several computers/ platforms.
As we will discuss in the next paragraph, these stages are important for understanding the characteristics of component software.
Component software. The central idea of component software is to structure
software systems in such a way that its components are subsystems following
the same principles as complete systems. Putting it the other way round, software components are systems that we can compose. In particular, software components do not only have interfaces to other software components, but also interfaces to their platform and environment. Furthermore, the same five stages that we
have described for complete systems apply to each component:
1. Component specification including interfaces and behavior
2. Component implementation
3. Component configuration
4. Deployed/installed components
5. Running components, often called component objects or instances
It is important to distinguish between components and their instances at runtime,
just as it is important to distinguish between a class and an object. Nevertheless, to
simplify the wording, we will often take the liberty to speak of components where
component instances are meant.
The notion of component is closely related to the notion of composition. Different
component models provide different forms of composition. Central questions are:
1. What is the result of composition steps?
2. How is composition reflected in the above stages?
3. How is composition described; what are its mechanisms?
19
20
Concerning the first question, we can distinguish at least three different kinds of
composition:
Hierarchical composition: The result of the composition of components C1,
..., Cn is a new component C following the same model as the sub-components
Ci. From C and other components, further components can be built in a hierarchical manner. The software system to be developed is the root component of
the resulting hierarchy. Although this seems to be the natural way to go, it is
usually not supported in existing component frameworks.
Heterogeneous composition: The result of the composition of components
C1, ..., Cn is a new component C with different characteristics than the subcomponents Ci. For example, the result could be a complete software system
that has well-defined interfaces, has independently described behavior, allows
for encapsulation, is independently deployable, but the description and composition techniques for the whole system are different than for the subcomponents. This is the approach underlying many component frameworks.
Open composition: The result of the composition of components C1, ..., Cn is
just a web of components, that is, a set of connected components. The environment of such a component web has access to all interfaces of all subcomponents Ci. The component web does not satisfy the criteria of components.
Often these approaches are combined, in particular in connection with the second
question referring to the stages at which composition happens. For example, we
could support hierarchical composition during the specification and implementation stages and additionally allow open composition at runtime. These aspects and
answers to the third question above are the topic of Section 2.3.3.
2.3
Software components
To get a more substantial picture of what software components are and as a background for the following sections, we investigate the basic notions underlying
software components along with a small, but non-trivial example.
ThermoControl example. The ThermoControl example features a thermostat
with a user interface that controls an air conditioner. Figure 2.2 shows the four
components. The AirConditioner can be in three modes: OFF, HEATING, COOLING. The mode can be set by sending the message switchTo with the appropriate parameter. The ThermoSensor measures the current temperature in its physical
environment. The temperature can be read by the method getTemp.
21
Figure 2.2:
The components of
the ThermoControl
example
The Thermostat is a re-active component. Internally, it has two variables (variables are depicted by squares): desiredTemp records the desired temperature,
which can be modified from the outside by calling modifyDesiredTemp. The
parameter value is added to desiredTemp. The variable currentACMode
records the current mode of the AirConditioner. The Thermostat repeatedly reads
the current temperature, compares it to the desired temperature, adjusts the AirConditioner accordingly, and notifies the ControlPanel in case changes have occurred.
Figure 2.3:
A simple GUI for
the ThermoControl
system
The ControlPanel is the user interface component to increase or decrease the desired temperature. Its display shows the measured and desired temperatures as
well as the current mode of the AirConditioner. Whereas its interface to the
Thermostat is a programming interface, the methods incTemp and decTemp and
the three depicted variables are part of a graphical user interface modeling what
22
users can do. Figure 2.3 illustrates a possible layout of such a GUI. To indicate
that the user interface is not a programming interface, it is highlighted in gray.
In Figure 2.2, the components are arranged in a way that suggests how they are
supposed to be composed. Connections and communication between components
happens via so-called ports. Ports that provide an interface are depicted as circles
(provided ports). Ports that need to be connected to provided ports are depicted by
semi-circles (required ports). The connection of ports is explained and discussed
together with other composition aspects below.
2.3.1
Component specification
23
interface ThermoSensorIF {
int getTemp()
// measures and returns current temperature;
// non-blocking;
// dependent on system environment;
// independent on component state;
}
Figure 2.4:
ThermoSensorIF
Figure 2.5:
ThermoSensor
24
Figure 2.6:
RecThermoSensorIF
Figure 2.7:
SensorHistoryIF
interface SensorHistoryIF {
model int maxTemp;
void setHistory( int startValue )
// only modifies maxTemp; non-blocking;
ensures maxTemp == startValue;
int maxMeasured()
// read-only; non-blocking;
ensures result == maxTemp;
}
The specification of SensorHistoryIF also illustrates other important properties of
methods in the context of stateful components. Methods can either have or not
have an effect on the state. Specifying that a method is read-only means that it
possibly reads the state, that is, it might dependent on the state, but it is not allowed to modify the state. Specifying that a method only modifies a certain part of
the state guarantees that other parts of the component state or of the component
environment are not effected.
Figure 2.8:
ThermoSensor2
component ThermoSensor2
implements RecThermoSensorIF, SensorHistoryIF
{
ThermoSensor2() // creates a component instance
}
Figure 2.8 shows a component declaration that makes use of the two interfaces
above. Similar to the treatment of methods in the context of multiple sub-typing in
Java, model variables with the same name are identified in the component; in par-
25
ticular, a component instance of type ThermoSensor2 has only one model variable
maxTemp. We also support component declarations with inlined interface and
model variable declarations. Figure 2.9 demonstrates this style by showing an
equivalent specification of the component ThermoSensor2. As the model variables have to be declared only once, this style is more convenient in cases with
complex state information.
component ThermoSensor2
implements RecThermoSensorIF, SensorHistoryIF
{
model int maxTemp;
interface RecThermoSensorIF extends ThermoSensorIF {
int getTemp()
// ... as in ThermoSensor
ensures maxTemp == max(result,old(maxTemp));
}
interface SensorHistoryIF {
void setHistory( int startValue )
// only modifies maxTemp; non-blocking;
ensures maxTemp == startvalue;
int maxMeasured()
// read-only; non-blocking;
ensures result == maxTemp;
}
ThermoSensor2() // creates a component instance
}
Types and data. In the context of component-based development, we can distinguish three kinds of types: data types, interface types, and component types. Data
types describe values that can be passed as parameters between components. We
assume that data types are passed by value. If mutable objects are used to represent data, these objects have to be copied when passed from one component to
another (similar to non-remote objects in Java RMI when passed to a remote
host). In particular, passing around data objects does not lead to new connections
between components. Usually, the primitive types of the used language are data
types (like int, boolean,...) or built-in types (like String). However, userdefined types may also be data types. A simple example is the type ACMode in
the ThermoControl scenario (see Figure 2.10).
Figure 2.9:
ThermoSensor2-a
26
Figure 2.10:
AirConditioner
}
At least on the specification level of components, it is very helpful to clearly distinguish between data types describing values that are passed between components, interface types declaring the communication protocols offered by an object
or a channel, and component types bundling provided and required interfaces together to form executable entities.
Component activity. We distinguish between passive and active components in
the MTC model. A passive component provides methods that can be called, but
has no thread of its own. For example, ThermoSensor and AirConditioner are passive components, only providing APIs to a sensor and an actor device, respectively. Active components have at least one thread running in the component
and are the source of activities in component systems. The thread is either started
at creation time of the component instance or by calling a dedicated method. In
the ThermoControl example, Thermostat and ControlPanel are active components.
Being passive or active is part of the component specification. Active components
are marked with the keyword active. (In our graphical representations, active
components are depicted with a bold line (cf. Figure 2.2 )).
The behavior of active components is usually more difficult to specify, because
internal activity and calls from other components can happen concurrently. The
activity is specified together with the method or constructor that creates the
thread. As a first example of an active component, we discuss the specification of
Thermostat given in Figure 2.11.
27
}
Thermostat(ACControlIF a, ThermoSensorIF t)
// constructor sets ac and ts and
// starts a nonterminating activity:
/* loop every x seconds {
int measuredTemp = ts.getTemp();
if( measuredTemp != desiredTemp &&
currentACM not appropriate
)
ac.switchTo( appropriate_mode );
if( (measuredTemp or desiredTemp or
currentACM have changed) and cp!=null )
cp.notify( measuredTemp, desiredTemp,
appropriate_mode );
*/
}
The component implements an interface to modify the desired temperature and to
register one control panel. It requires the connection to components implementing
the interfaces ACControlIF and ThermoSensorIF. The multiplicity [1] in
the requires-declaration states that these connections have to be established prior
to using the component and prior to starting its activity. That is why these connections are established by the constructor. The multiplicity [0..1] indicates that the
component implementing the interface ControlPanelNotifIF could be connected later. The rest of the specification should be self-explanatory. When studying the specification, three aspects should become clear:
1. Compared to class declarations, component specifications are more explicit
about their connections to other components. This simplifies the distinction
between internal state and external connections and makes it easier to check
design principles (for example that the connections are only done to interface
types and that components may not be used as method parameters).
2. Together with the specifications of the used data and interface types, specifications are self-contained.
3. Implementations of active components have to be careful regarding multithreading and synchronization. In the example, the activity thread of the
Thermostat runs concurrently with threads that call the methods modifyDesiredTemp and registerGUI and that modify parts of the shared component state.
active component ControlPanel
implements ControlPanelNotifIF
{
requires[1] ThermostatModifIF t;
provides[1] ModifyTempIF gui; // implicitly provided
visible int measuredTemp;
visible int desiredTemp;
visible ACMode controlLamp;
Figure 2.11:
Thermostat
28
Figure 2.12:
ControlPanel
}
Further specification aspects. In this short introduction to specification techniques for components, we could only consider basic aspects in an informal way.
In Section 2.4, we explain semantic properties related to component models in
more detail. Here, we like to discuss further aspects to extend the expressivity of
the sketched specification techniques:
We claimed that there is a clear distinction between value, interface, and component types. A more comprehensive specification technique has to establish
the relationships between objects of these types and has to provide constructs
for handling composed date types and expressing data abstraction.
We encourage the consideration of user interfaces as components and specifying their behavior with the technique that we have sketched so far. Figure
2.12 illustrates such a specification for the ControlPanel component. The buttons that the GUI provides are modeled as an interface. This interface is implicitly provided by the component. Furthermore, the variables marked as
visible are supposed to be visible in the view of the GUI.
In the examples above, the object created when a component is instantiated
implements the provided interfaces of the component. A slight exception is the
ControlPanel component, as the object of the component type ControlPanel
does not implement ModifyTempIF. In general, components should be able
to provide interfaces dynamically. This can be handled in the specification
technique by methods returning object references of an interface type (that is
not a value type).
Although we have been explicit about concurrency issues (there is a lot of
work about components that avoids this crucial topic), we have not discussed
synchronization and more complex communication aspects. For example,
callback scenarios are difficult to handle in specifications. A callback sce-
2.3.2
Component implementation
29
30
currentACM = ACMode.OFF;
new Thread(this).start();
}
public void registerGUI( ControlPanelNotifIF cp ) {
guicp = cp;
doUpdate = true;
}
public void run() {
while (true) {
int newTemp = sensor.getTemp();
if (newTemp != currentTemp) {
currentTemp = newTemp;
doUpdate = true;
}
if (desiredTemp < currentTemp &&
currentACM != ACMode.COOLING) {
aircon.switchTo(ACMode.COOLING);
currentACM = ACMode.COOLING; doUpdate = true;
} else if (desiredTemp > currentTemp &&
currentACM != ACMode.HEATING ) {
aircon.switchTo(ACMode.HEATING);
currentACM = ACMode.HEATING; doUpdate = true;
} else if (desiredTemp == currentTemp &&
currentACM != ACMode.OFF ) {
aircon.switchTo(ACMode.OFF);
currentACM = ACMode.OFF; doUpdate = true;
}
if( doUpdate && guicp != null ) {
guicp.notify(currentTemp,desiredTemp,currentACM);
}
doUpdate = false;
try {
Thread.sleep(1000);
} catch( InterruptedException e ) {}
}
}
public void modifyDesiredTemp( int t ) {
desiredTemp += t;
doUpdate = true;
}
Figure 2.13:
ThermostatImpl
Figure 2.13 shows a Java implementation for the Thermostat component (access
modifiers are left out wherever possible). The component is realized as a class.
The connections to the required interfaces and the model variables are represented
by fields. The (re-)active behavior of the Thermostat is implemented by a component-local thread that measures the temperature every second, updates the AirConditioner accordingly, and notifies the ControlPanel if a change has occurred.
The thread is started after the initialization of the strictly required connections. As
the multiplicity of the ControlPanel is zero or one, we can start the thread at the
end of the constructor and register the ControlPanel later.
According to the overall usage scenario as depicted in Figure 2.2 and the specification of the Thermostat, there are at least three threads that can modify the state
of a Thermostat, namely the Thermostats own thread, the thread of the ControlPanel (possibly modifying the desired temperature), and the thread that registers
ControlPanels (possibly modifying the field guicp). To synchronize the modifications of the fields between the threads, the fields are declared as volatile.
Without such a declaration, it could happen according to the Java semantics
that a modification by one thread remains invisible to others. Of course, in more
complex component scenarios, more sophisticated synchronization is needed, in
particular to avoid deadlocks.
2.3.3
Component composition
31
32
Figure 2.14:
ThermoControlGlu
eCode
33
structor of the Thermostat component. This is possible because there are no mutual dependencies. The composition of the Thermostat and the ControlPanel cannot be done within the constructors, because these components have a mutual dependency. The solution for their composition is asymmetric. Connecting the required port of the Thermostat to the ControlPanel is done in the constructor of the
ControlPanel, whereas the ControlPanel is connected to the Thermostat by the
method registerGUI. As we need both ports of the Thermostat, variable t
cannot be of the interface types provided by the ports, but has to be of the component type.
The example demonstrates composition on the instance level and yields a web of
four components. In particular, the result of the composition is not a component
by itself. The glue code describes a composition at runtime. Even in this simple
example, we can observe aspects of non-uniform composition descriptions: The
components ThermoSensor and AirCondition are only handled by one of their
provided ports, whereas for Thermostat we had to use the implementation type.
Most compositions are performed by the constructors, but one connection had to
be established by a special method. Another source of non-uniformity often comes
from the different possibilities to start components' activities. In the example, the
activities are started by the constructors of Thermostat and ControlPanel. Often,
the startup has to be done explicitly by the glue code after the composition is
completed (note that Thermostat is started before that). As non-uniform composition is not feasible for component frameworks, they usually define a standard for
composition and require that components provide special interfaces for composition (similar to RegisterIF) and startup (see Chapter 4 for composition standards and Chapter 6 for automated composition techniques and dependencies injection).
Figure 2.15:
The
ThermoControl
system composed
out of the four
components
34
To illustrate the construction of new components from given ones, that is, hierarchical or heterogeneous composition, we specify a new component TCSystem,
which encapsulate the complete thermo-control system and only provides the user
interface. Figure 2.15 depicts the overall component. Figure 2.16 provides the
specification for that component. As it is often the case for composed systems, the
overall behavior is explained in terms of the subsystems, even if the subsystems
are encapsulated. The implementation of the components consists of the code for
the constructor TCSystem, which is identical to the glue code of Figure 2.14. In
general, the implementation of a composed component CP consists of glue code
for composing the subcomponents and of code realizing additional functionality
of CP.
Figure 2.16:
TCSystem
}
To illustrate further composition aspects, we consider a distributed scenario where
the ControlPanel and the Thermostat run on different devices. Again, we first look
at the Java implementation and then discuss related requirements for component
frameworks. Unfortunately, we cannot use the component implementations as
they are, but have to modify their code to make them fit for distribution. All provided interfaces of the components have to implement the Java library type Remote; a number of additional exceptions have to be handled; and the components
have to be extended or wrapped to be accessible remotely. Note that these changes
can cause further modifications.
try {
String tsurl = ... // URL of local device
String cpurl = ... // URL of ControlPanel device
boolean connected = false;
ThermoSensorIF s = new ThermoSensor();
ACControlIF
a = new AirConditioner();
Thermostat t = new Thermostat(a,s);
ControlPanelNotifIF c = null;
Naming.rebind(tsurl+"thermostat", t );
do {
Thread.sleep(1000);
try {
c = (ControlPanelNotifIF)
Naming.lookup(cpurl+"controlpanel");
connected = true;
} catch( NotBoundException nbe ) {
System.out.print("Binding failed; try again");
}
} while ( !connected );
t.registerGUI( c );
} catch( Exception ioe ) {
System.out.println("Connection failed"); ...
}
More substantial are the changes to the glue code, in particular because the considered components ControlPanel and Thermostat are mutually dependent. Figure
2.17 presents the glue code for the platform of the Thermostat. After the creation
of the three local component instances, the Thermostat instance t is registered at
the local name server (rmiregistry) under the name thermostat. Then, a loop is
started that tries every second to look up the remote object of the ControlPanel in
the remote registry under the name controlpanel. If the lookup is successful, the
remote port object c is registered at t. The glue code for the ControlPanel device
is similar.
Component frameworks should make it easy to go from local execution to distributed scenarios. They should automatically generate the needed implementation
parts or provide an appropriate infrastructure. Furthermore, they should allow migrating components at runtime, for example for load balancing in computationally
intensive application scenarios. To achieve support, component frameworks have
to fix design decisions concerning the component descriptions and the implementation technology. We will discuss solutions to these problems in later chapters of
the book.
2.3.4
Deployment
Deployment essentially means bundling the program code of the components and
possibly the glue code into deployable units and making these units available on
the target platforms. Deployment depends on many technical aspects, in particular
on the implementation language, the configuration techniques, and the platform
properties. Here, we focus on the aspect that is most specific for CBSD, namely
the bundling of program code to deployable units. As our examples are based on
Java technology, it is clear that the deployable units are bundled as jar-archives
and that they are usually made available on a platform by copying them into a di-
35
Figure 2.17:
ThermoControlRe
moteGlueCode
36
rectory that is in the class path of the executing virtual machine. And following
the principle that components are independently deployable, there has to be at
least one bundle for each component.
The central question remaining is: What belongs to a code bundle of a component? Unfortunately, this question has no simple and general answer. Of course,
the bundle for a component contains the class that implements the component. For
example, the bundle for the Thermostat contains the class Thermostat. But, already for the interfaces ThermostatModifIF and RegisterIF, it is less clear if they
should be part of the bundle. There might be other components also implementing
these interfaces, so that the assignment of the interface declarations to the bundle
of only one of these components seems inappropriate. However, if we place the
declarations into several bundles, we have to make sure that they are considered to
be the same if more than one of these bundles is used. In a first approach, we distinguish the following six code categories in an implementation CPI of a component CP and sketch how they could be handled:
1. Library code: A CPI might use standard or generally available library or
framework code. This should be made available on the platform to all components and need not be part of CPIs bundle. (E.g., the components of the
ThermoControl example used Java library code from the io-, swing-, and rmipackages)
2. Dedicated component code: The core part of a CPI consists of code that is
only used by this component. It should be encapsulated and is definitely part
of CPI's bundle. (E.g., the code of the component constructors)
3. Code of subcomponents: Subcomponents have their own code bundles. Here,
a decision has to be made about whether the code bundles of subcomponents
should be deployed separately or whether they should be part of the composed
component's bundle. Depending on the component system, both options could
be reasonable.
4. Shared hidden code: Different components (of a special domain) might share
implementation parts that are not component implementations and are not
generally available on the platforms. As in item (3.), different options are possible and reasonable.
5. Dedicated interfaces and data types: Interface and data types that are visible
in the interface of a component CP and that are only implemented by CPI
should be part of the CPI bundle.
6. Shared interfaces and data types: Interface types that are implemented or
extended by several components, and data types for complex values that are
interchanged between a number of components, should be kept in separate
header bundles. (E.g., interface type ThermoSensorIF is used in component
ThermoSensor and ThermoSensor2.)
The simple bottom line of the discussion of the code categories is that there is no
direct mapping from code parts to components. Components usually share code
and this sharing has to be managed during the build and deployment process. In
addition to this, deployment has to take care of the glue code in open composition
settings.
2.4
Component models
2.4.1
37
38
Figure 2.18:
A component in
stylized notation
2.4.2
39
40
2.4.3
A better separation of components can be achieved by communicating via messages. Asynchronous messages are like emails sent by the sender component CS
to the receiver component CR. After sending the message, the sender can immediately continue with its activity, not waiting for any synchronization with the receiver. At the receiver site, a message queue records the incoming messages. The
receiver component can decide in which order or whether at all it likes to act to
received messages. In the ACC model, all components are active as they are supposed to read and react to incoming messages.
Figure 2.18:
The
ThermoControl
example in the
ACC model
the message TempMsg contains the offset for modifying the desired temperature
or the current temperature measured by the ThermoSensor.
Interface methods with return values are split into two messages. For example, the
message getTemp is realized by the message GetTemp, which requests the
ThermoSensor to send the current temperature. The ThermoSensor is expected to
answer with a message TempMsg(MsgKind.CURRENTTEMP,x), where x is
the measured temperature value.
Implementation. The typical implementation pattern of a component in the ACC
model consists of a loop that reads from the incoming message queue and reacts
to the messages by modifying the internal state and sending messages. In particular, the thread(s) running in such components never leave the component. Their
activities remain local. Synchronization between components is handled by the
queues. Component implementations do not need to worry about this.
class Thermostat extends LinkedBlockingQueue<TempMsg>
implements Runnable {
BlockingQueue<Notify> tocp;
BlockingQueue<GetTemp> tots;
BlockingQueue<SwitchTo> toac;
int desiredTemp = 20;
ACMode currentACM = ACMode.OFF;
boolean doUpdate = false;
...
public void run() {
int currentTemp = 0;
TempMsg currmess;
while (true) {
try {
tots.put(new GetTemp()); // ask for the temperature
currmess = take();
while(currmess.mname==MsgKind.MODIFYDESIREDTEMP){
desiredTemp += currmess.tempval;
currmess = take();
}
assert currmess.mname == MsgKind.currentTemp;
int newTemp = currmess.tempval;
if (newTemp != currentTemp) {
currentTemp = newTemp;
doUpdate = true;
}
if( desiredTemp < currentTemp &&
currentACM != ACMode.COOLING) {
toac.put(new SwitchTo(ACMode.COOLING));
currentACM = ACMode.COOLING; doUpdate = true;
} else if( ... ) {
...
}
if (doUpdate) {
tocp.put( new Notify(currentTemp,
desiredTemp, currentACM));
41
42
Figure 2.19:
ThermostatImplAC
C
}
doUpdate = false;
Thread.sleep(1000);
} catch (InterruptedException e) {}
} } }
...
}
natural way. Furthermore, the component has better control over incoming messages. It can decide when or whether at all it wants to react to messages. This is
more difficult to achieve in the MTC model. The disadvantage of the ACC model
is that the indirection caused by using messages and queues often reduces efficiency and requires that the programmer explicitly distinguish between messages
and methods. In addition to that, the connection between call and return simplifies
to handle closer coordination between components.
As Java concurrency is based on multi-threading, the realization of ACC in Java
looks a bit unnatural. Other languages and some Java extensions support ACC
directly. Maybe the most prominent language based on the ACC model is Erlang
([7]). Languages like Java support implementations according to the ACC model
with classes in the library. The Java Enterprise Edition provides the Java Message
Service (JMS) for communicating via asynchronous messages. Furthermore, there
are a number of Java extensions with built-in constructs for asynchronous communication (see, e.g., [11] and [37]).
2.4.4
43
44
Figure 2.20:
The
ThermoControl
example in the
SCC model
The SCC model simplifies the synchronization of components. On the other hand,
the risk of deadlocks is higher in SCCs than in ACCs. A typical deadlock arises if
a component C1 blocks because it waits to receive a message from component C2
while C2 blocks waiting to receive some message from C1. To show the absence
of deadlocks, it is helpful to abstract the component behavior into so-called interface automata. Interface automata are also helpful as a specification technique for
synchronously communicating components.
An interface automaton is a finite automaton without final states. Transitions in
interface automata are labeled with message names suffixed by ? for receive
messages and by ! for send messages. In addition, the label may be used to
indicate an internal transition of the component. An interface automaton abstracts
from the internal behavior and the data manipulation of a component. Interface
automata were introduced in [5] to precisely study an optimistic approach to composition.
Figure 2.21 demonstrates interface automata along with the ThermoControl example. Messages are abbreviated as shown in the upper left corner of the figure.
Channels are annotated with the messages they allow. The direction of message
flow is the same as in Figure 2.20. The behavior of each component is modeled by
an interface automaton. The states of the automata are depicted by small circles;
starting states are indicated by a short arrow (without a source state). -labeled
transitions may always be taken. A transition with a receive message label m? in a
component Crcv may only be taken if
Crcv is a receiver at a channel for m and
45
Figure 2.21:
acInterfAutom
Implementation. A simple way to implement synchronous communicating behavior in object-oriented languages is by using appropriate channel classes instead
of message queues. For example, Java provides the blocking queue class SynchronousQueue<E> in the package java.util.concurrent, in which each putoperation must wait for a corresponding take-operation, and vice versa. A synchronous queue does not have any internal capacity, not even a capacity of one.
Thus, it can be used as a synchronous channel.
BlockingQueue<Notify>
s = new ThermoSensor(inchTS,inchTh);
Figure 2.22:
ThermoControlSC
CGlue
46
Whereas we assumed in the last subsection that the input queues are part of the
components that read them, we demonstrate here how connecting is done in a scenario in which the connectors/channels are provided by the glue code describing
the composition. For our running example, the creation of channels and component instances can be realized by the code fragment of Figure 2.22. The component constructors take the channels as parameters and establish the connections.
The method implementations in the SCC version of the classes are almost identical to the ones of the ACC version. However, this is not a typical case. In other
scenarios, implementations can differ a lot between asynchronous and synchronous versions.
Discussion. Like the ACC model, the SCC model achieves a better separation of
components than the MTC model. The channels play the role of connectors between components. Compared to ACC, the SCC model simplifies the coordination
between the activities in the different components. This is one of the main reasons
why many calculi for concurrency use synchronous communication, in particular
CSP (communicating sequential processes, see [24]), CCS (calculus of communicating systems, see [30]), and the -calculus (see [31]). A disadvantage of this
closer cooperation is the higher risk of deadlocks.
2.4.5
47
Figure 2.23:
The
ThermoControl
example in the
CSC model
To realize the ThermoControl example based on the CSC model, we provide each
component with variables for output. These variables are read from other components in the first step of a clock cycle. We assume here that reading is done by a
method call. Of course, we could have allowed direct access as well. For example,
ControlPanel has a variable desiredTemp and provides a method readDesiredTemp. Figure 2.23 gives the complete picture for the ThermoControl example.
public void readStep() {
desiredTemp = guicp.readDesiredTemp();
measuredTemp = sensor.readTemp();
}
public void modifyStep() {
if(
desiredTemp < measuredTemp &&
currentACM != ACMode.COOLING) {
currentACM = ACMode.COOLING;
} else if( desiredTemp > measuredTemp &&
currentACM != ACMode.HEATING ) {
currentACM = ACMode.HEATING;
} else if( desiredTemp == measuredTemp &&
currentACM != ACMode.OFF ) {
currentACM = ACMode.OFF;
}
}
It is interesting to compare the needed functionality of the read step and the compute step of the Thermostat component with the run method of the implementations according to the MTC and the ACC models (see Figures 2.13 and 2.19
Figure 2.24:
ThermostatImplCS
C
48
resp.). As Figure 2.24 illustrates, the functionality is reduced to the core behavior.
All coordination is implicitly managed by the clock-based synchronization.
Discussion. The CSC model is mostly used in areas where software systems are
developed for dedicated hardware, like in embedded system development. If the
machine clock can be used to realize the virtual clock underlying the CSC model
and if the concurrent components can be placed on concurrently executing processors, system implementations according to the CSC model are very time- and resource-efficient. Furthermore, the CSC model simplifies static analysis because
the timing behavior is easier to predict and because it has less sources of nondeterministic behavior than the other models in which the (implicit) scheduler has
a strong influence on the execution.
The CSC model is less appropriate in distributed settings with communication delays and for applications in which the computation time needed by different components for one step varies considerably, or in which complex data has to be communicated between components. Another disadvantage of the CSC model is that it
is less clear how to use it together with software according to the other models.
2.5
49
50
The mechanism for remote method invocation in Java, RMI, is a typical example
of a communication infrastructure. It provides library classes, interfaces, and services to connect to remote objects and invoke their methods. It can be used to
build up a component infrastructure, but it is not a component framework. Most
notably, a component model is missing.
The AWT is a typical program framework. It has an implicit, albeit very weak notion of components, namely the subclasses of the class Component. However, it is
missing a component infrastructure that would provide rules and techniques for
composition and deployment. The programmer is left with the raw class declarations to understand the interfaces of newly developed AWT components. That is
why Sun Microsystems developed JavaBeans (JavaBeans should not be confused
with Enterprise JavaBeans; both frameworks are very different and share almost
nothing except the name). The JavaBeans framework builds on the AWT by making the component model somewhat more explicit and by providing a light-weight
infrastructure. In particular, it defines rules and conventions for component interfaces and supports an event-based composition mechanism.
2.6
Problem 2.1:
Differences
between
component models
Problem 2.1:
Problem 2.2:
Kinds of
composition
Problem 2.2:
Problem 2.3:
Component
declarations and
classes
Problem 2.3:
Problem 2.4:
Component
specifications
Problem 2.4:
Problem 2.5:
Multi-threading in
CBSD
Problem 2.5:
What kinds of composition are explained in the text? Describe the differences!
What are the commonalities of and the differences between component declarations and classes?
What are the problems caused by multi-threading in CBSD? Explain why several
attributes of the class Thermostat (Figure 2.13) are declared as volatile.
51
Problem 2.6:
How is synchronization between component activities established in the ACC
model?
Problem 2.7:
Can there be deadlocks in the CSC model? If the components in the CSC model
have to compute tasks of very different size and complexity, the central clock can
be a burden. Why?
Problem 2.6:
ACC model
Problem 2.7:
Deadlocks in the
CSC model
52
53
54
3.1
3.2
An explanation of CBSD
Whereas CBSD is often interpreted very broadly, we focus here on the core aspects and present a quite narrow, product-oriented notion. The reason for this approach is that, rather than selling CBSD, our main goal is to come up with crisp
criteria to discuss what CBSD is and what it isnt.
A component-based software system is a software system whose implementation is composed from software components that
have well-defined interfaces and a behavior that can be described independent of the contexts in which they are used;
have an implementation that can be composed and deployed independently;
follow a component model that determines the common computation, communication, and composition principles underlying a class of components. For
large systems, a number of different component models can be used.
Furthermore, we require that the component structure refines the (described) architecture of the system.
To check whether a given software system follows the explanation, one should
answer the following questions:
1. What are the components of the system? The answer should be a finite list of
named components.
2. What are the interfaces of the components, what is their behavior? Ideally, the
resulting component specifications should not refer to specifications of other
components.
3. Which component model is used?
4. Does every component have an independently deployable implementation?
The answer could be a finite list of deployable units for each component.
5. What are the static and dynamic component structures of the system? Here,
static means that a component might be implemented with the help of other
components (cf. Figure 2.15 in Section 2.3.3). Dynamic means the possible
connections that component instances might have at runtime. It is important
that the structures are defined in terms of the component names given in step
(1.).
6. How is the composition described? Does it follow the component model?
7. Do the static and dynamic component structures of the system refine the described architecture? (More on this aspect can be found in Section 3.4.)
Our experience is that
before asking these questions, system architects tend to say that their software
system is component-based;
only very few systems satisfy all criteria.
Let us stress again that the idea of the explanation is not to say that a system is
badly designed if it does not follow all criteria. The criteria are meant to distinguish designs that are essentially component-based from those that take the notion
of software components less strictly or follow other disciplines. Based on the notion of component-based software systems, we can explain what CBSD is about:
Component-based software development is a sub-discipline of software engineering. Its goal is to provide concepts, methods, languages, techniques, frameworks, tools, standards, and an organizational infrastructure for the development,
provision, and selection of reusable software components as well as for the development of component-based software systems (in the above sense) using these
components.
Whereas our explanation is narrow with respect to software components and
component-based software systems, it is quite liberal with respect to the development processes for such systems. In particular, we do not restrict CBSD to search,
composition/assembly, and development of reusable components. As explained in
the following section, our explanation also covers top-down developments if they
result in a component-based software system.
55
56
3.3
One central topic of software engineering is concerned with organizing the development or evolution of software systems into processes with well-understood
tasks and (intermediate) products. In this section, we discuss such engineering aspects of CBSD. In particular, we relate component-based development of new
software systems to traditional software development, sketch aspects of the evolution of component-based software and of the development of components, consider component selection and adaptation, and discuss quality assurance in the
context of CBSD.
3.3.1
Software system development is based on four kinds of tasks: requirements analysis, design, implementation, and deployment. An implicit assumption underlying
the classical development process and more sophisticated variants of it is that the
implementation of the new software system is developed from scratch. Component-based software development starts with a different picture. The construction
of the new system should be accomplished by using existing software components. This goal shifts the focus of CBSD to two engineering problems:
1. Development with reuse: How can we bridge the gap between the requirements for a new system and existing components that can possibly be reused
to implement the system?
2. Development for reuse: How can we develop components such that they can
be reused? Here, reuse includes the task of finding components and checking
whether they satisfy the needs.
The first question addresses CBSD in top-down software development. The requirement to reuse components can be understood as a development constraint. To
solve the constraint, the software architecture has to be designed in such a way
that existing components can be plugged into the system. The second question
stresses bottom-up development where the constraint is reusability. Again, software architecture plays an important role, because reusability can rarely be achieved without suitable architectural information like a reference architecture, architectural patterns, or a component framework supporting architectural styles. As
software architecture and related issues are so important for CBSD, we treat them
in a separate section (Section 3.4).
Although CBSD has to address additional engineering aspects, most tasks of
CBSD can be accomplished using existing software engineering processes and
methods. Whether a certain process is appropriate depends on the task at hand.
Development scenarios with minor reuse can be treated as in traditional software
development (see, e.g., [17] for a software engineering approach that takes components into account). The main difference is that the resulting architecture and
3.3.2
57
58
3.3.3
The development of components is similar to the development of small or medium-size software systems. Having a given, clear component model is helpful to
develop both the component specification and its implementation. Methods and
processes for component development are discussed, e.g., in [17] and [39] and are
not part of the scope of this textbook. What we like to stress here is the importance of CBSD for role separation and outsourcing of implementation tasks.
Role separation that is, structuring the engineering task into groups of developers and technicians with different skills, from different companies, and working
for different salaries is very important. CBSD supports this structuring in several ways. For outsourcing to other development teams, the specification together
with the component model and the component framework can be used as a clear
contract between the component vendor and the buyer. The buyer obtains a component that she can test against the specification. Independent deployability is very helpful for system integration. Furthermore, the strict structuring of CBSD, in
particular in connection with component frameworks, allows separating the development and maintenance of a system into five different roles: component provider/vendor, framework provider, application provider (constructs an application
from a number of components), application deployer (installs an application on a
platform), and system administrator.
3.3.4
scriptions, or even based on behavioral properties (see, e.g., [42]). So far, there is
no widely accepted infrastructure for component retrieval.
The next step after having retrieved a number of candidates is component qualification. This is the process of determining which candidates are suitable for use
within the intended final system. Qualification includes
checking that the found components really have the properties used in the
search,
comparing the components in order to evaluate which component fits best,
testing and analyzing the chosen component(s) to make sure that their implementation satisfies their specification and further properties stated for the
component.
The last point is particularly important if the components come from third parties
or even untrusted sources. For test and analysis techniques, we refer to the subsection on quality assurance below.
In many cases, selected components cannot be used directly. They need adaptation before they can be integrated into the system or assembled with other components. Adaptation means modifying functional, non-functional, or technical
properties of a component such that the adapted component fits the needs of the
composition context. Adaptation techniques depend on the representation of the
component. In particular, we can distinguish:
White-box adaptation assumes that the implementation of the component is
available; it makes it possible to modify the program code of the component.
Gray-box adaptation is possible if we can wrap and extend the given component by some implementation parts without modifying the code of the component; the new wrapper together with the old component implementation
should form a software component, but the wrapper itself need not be a component.
Black-box adaptation takes the old component as given and implements a
new component that uses and possibly encapsulates the old component.
Some adaptations, in particular on the syntactical level and for technical properties, can be done automatically. Typical examples are renaming of interface types
and methods, integrating components written in different languages (e.g., embedding components written in C into a Java language context), and making components accessible from remote sites.
59
60
3.3.5
Quality assurance
3.4
(cf. Section 2.2 for open composition) and if the composition merely aggregates functionality, but is not designed to satisfy overall system requirements.
Typical examples are applications running on a PC or a mobile phone, coupled
(if at all) only by data exchanged over a file system or by other services of the
underlying platform. (Note, however, that in many professional scenarios,
PCs, laptops, or mobile phones are integrated into the workflows of larger systems.)
Protocol and data integrated systems: Software architecture is important for
systems that exchange data with system-specific formats and system-specific
protocols. Such systems have to guarantee system-wide invariants. Typical
examples are information systems such as the retail store example discussed in
Section 1.3. In such systems, it is relatively simple to integrate components
from different vendors, because the real-time constraints are low and they either run on general-purpose computers (like the database component of the
store management; see Figure 1.2 or they come with their own hardware (like
the scanner). The software architecture defines for a component provider how
its components can be integrated into the system.
Embedded systems: In many embedded system scenarios, the coupling constraints between components go beyond protocols and data formats. To
achieve integration, the software and system architecture has to cover realtime aspects and platform properties in much greater detail. For example, the
Java components for the thermo-control system considered in Section 2.3
might be worthless because the hardware for running a Java Virtual Machine
might be too expensive for a simple thermo-control device.
At least the last two scenarios should have illustrated that the integration of components into a system calls for more than just the specification of components. In
the next two subsections, we consider the relationship between CBSD and software architecture and between CBSD and other relevant development or architectural techniques.
3.4.1
Before we look into the relationship between CBSD and software architecture, we
will consider three prominent explanations of what software architecture is.
Then we will discuss the special architectural aspects of CBSD.
In Section 2.1 of [9], page 23, the following explanation of software architecture
is given:
The software architecture of a program or computing system is the structure or
structures of the system, which comprise software components, the externally visible properties of those components, and the relationships among them.
61
62
The definition is given together with further explanation parts from which we
would like to cite as well:
"Externally visible" properties refers to those assumptions other components can
make of a component, such as its provided services, performance characteristics,
fault handling, shared resource usage, and so on. The intent of this definition is
that a software architecture must abstract away some information from the system
(otherwise there is no point looking at the architecture, we are simply viewing the
entire system) and yet provide enough information to be a basis for analysis, decision making, and hence risk reduction. Let us look at some of the implications of
this definition in more detail.
First, architecture defines components. The architecture embodies information
about how the components interact with each other. This means that architecture
specifically omits content information about components that does not pertain to
their interaction. Thus, an architecture is foremost an abstraction of a system that
suppresses details of components that do not affect how they use, are used by, relate to, or interact with other components. [...]
Second, the definition makes clear that systems can comprise more than one
structure, and that no one structure holds the irrefutable claim to being the architecture.
[...] By intention, the definition does not specify what architectural components
and relationships are. Is a software component an object? A process? A library?
A database? A commercial product? It can be any of these things and more.
Third, the definition implies that every software system has an architecture, because every system can be shown to be composed of components and relations
among them. [...]
Fourth, the behavior of each component is part of the architecture, insofar as that
behavior can be observed or discerned from the point of view of another component. This behavior is what allows components to interact with each other, which
is clearly part of the architecture. Hence, most of the box-and-line drawings that
are passed off as architectures are in fact not architectures at all. They are simply
box-and-line drawings. [...]
The introduction of [38], page 3, gives the following explanation for software architecture:
The architecture of a software system defines that system in terms of computational components and interactions among those components. Components are
such things as clients and servers, databases, filters, and layers in a hierarchical
system. Interactions among components at this level of design can be simple and
familiar, such as procedure call and shared variable access. But they can also be
complex and semantically rich, such as client-server protocols, databaseaccessing protocols, asynchronous event multicast, and piped streams.
In addition to specifying the structure and topology of the system, the architecture
shows the correspondence between the system requirements and elements of the
constructed system, thereby providing some rationale for the design decisions. At
the architectural level, relevant system-level issues typically include properties
such as capacity, throughput, consistency, and component compatibility.
In [16], a book concentrating on patterns, the following definition can be found on
page 384:
A software architecture is a description of the subsystems and components of a
software system and the relationships between them. Subsystems and components
are typically specified in different views to show relevant functional and nonfunctional properties of a software system. The software architecture of a system
is an artifact. It is the result of the software design activity.
If we talk about the architecture of a software system, we refer to the different implicit and explicit structures underlying the system. Structures are expressed as
certain kinds of components not necessarily software components in the sense of
Chapter 2 and their connections or relations, i.e., the architecture captures system properties that are beyond algorithms and data structures. This is the focus of
the first definition and is a central part of most explanations of software architecture.
The second definition mentions another important and often neglected point. Architecture is about relevant system-level or system-wide issues and how they are
realized within the system. In addition to the properties named above, these include aspects like security, system availability, and fault tolerance. Thus, architecture is not only about the structuring of systems, but also captures the relationship
between the overall system behavior and the structured realization.
The third definition contains a remark about the nature of architectures: They are
artifacts, i.e., something explicit, the result of an activity. Notice that this idea is
contradictory to the third implication in the explanation of [9] saying that every
software system has an architecture, possibly an implicit one. However, it is an
important issue that architectures can be artifacts in their own right. They can be
designed and studied without going down to concrete realizations in the form of
software systems.
Architectural views. When it comes to developing a description of an architecture and to analyzing it, one has to select certain system structures and give their
components and relations a clear meaning. If an architecture is to be used as a
communication document between different stakeholders, it is important to find
some standards to select these structures. That is the goal of authors who propose
describing software architectures based on a fixed number of so-called views. An
architectural view concentrates on specific properties of the architecture or looks
at the architecture from a certain perspective. A view may combine several structures. In the literature, different views have been investigated. There is not yet a
63
64
3.4.2
CBSD is not the only development technique that aims to improve reuse and
software evolution. In this section, we sketch the relationship of CBSD to:
Service-oriented development and service-oriented architecture (SOA)
65
66
AOSD and AOP can be considered as being orthogonal to CBSD. Whereas CBSD
stresses strict partitioning of the system's functionality into deployable components, AOSD supports the treatment of system-wide aspects as layers that can be
automatically added to software components. That is, CBSD and AOSD are complementary techniques that should be used together.
Model-driven development. Model-driven development structures the development process of software systems and single components into refinement steps. It
starts with a model that captures the central system behavior in an abstract way,
focusing on what happens and not on how it is computed (CIM). The next model
level describes how such systems can be realized in a platform-independent way
(PIM). By refining this model with respect to particular platforms, a platformspecific model (PSM) is constructed. This model is used to generate and program
the needed system-specific code. Model-driven development is very helpful if
systems or components are to be implemented for very different platforms and if
the application area allows automating model transformations or generating a substantial part of the code.
Whereas CBSD emphasizes the structuring of systems into software components,
model-driven development focuses on structuring the development process into
several refinement steps. Thus, both development techniques are orthogonal and
can be combined (see [39] for model-driven development of components). The
terminology of model-driven development allows us to explain more precisely
what we meant by ..., we require that the component structure refines the (described) architecture of the system in the explanation of component-based software systems in Section 3.2. In CBSD, we expect that the component boundaries
determined and designed on the CIM-level remain the same throughout the refinement process down to the deployable code.
Product-line engineering. Product-line engineering develops architectural structures and software artifacts for families of software products within a company or
organization. The idea is that similar products can be developed and maintained
more cost-effectively if they share a common design and common parts of the implementation. The development of a product line includes in particular:
Scoping, which determines the products covered by the product line
Feature modeling, which describes the set of features available in products
A product-line architecture, which explains how the artifacts of a product
line are related and can be composed
Implementation artifacts and components, which allow deriving single
products of the product line.
Product-line engineering is an approach for the planned and effective reuse of
software artifacts and components. Rather than developing software components
for general use, product-line engineering creates software artifacts when reuse can
67
68
be predicted. Product-line engineering uses component-based software technology, in particular for product derivation, but exploits other forms of generic and
composable software artifacts as well.
3.5
3.6
Problem 3.1:
Design with reuse
and design for
reuse
Problem 3.1:
CBSD is about design with reuse and design for reuse. Explain these terms and
relate them to top-down and bottom-up software development methods. What is
the role of architecture in the two development scenarios?
69
Problem 3.2:
Name the three main reasons for software evolution and why CBSD can help to
perform evolution steps. Name situations in which CBSD cannot be of help for
evolution.
Problem 3.3:
Problem 3.2:
Software evolution
Problem 3.3:
Role separation
Explain role separation and name five different roles. Why is role separation important for software development?
Problem 3.4:
Explain the three adaptation techniques mentioned in the text and how they differ.
Problem 3.5:
What are the differences between product-line engineering and CBSD?
Problem 3.4:
Adaption
techniques
Problem 3.5:
Product-line
engineering and
CBSD
70
4.1
71
72
4.2
As introduced in Section 2.5, components are the units of composition in the context of a component framework. The goal of component frameworks is black-box
reuse. That is, components are reused based on their specified interfaces and their
explicit context dependencies only where context dependencies capture the requirements made on the environment in which a component executes. Inspection
of the component's implementation by the user should be unnecessary and is often
not possible.
A component framework CF supports the definition of components as well as
their deployment, connection, and use. In particular, a component framework has
to answer the following questions for component developers and users:
How are components named and identified?
What descriptions are needed to develop a component?
How are components deployed?
How can components be found and used by other components?
What platform and runtime support is needed to work with a particular component framework?
Before explaining in the following sections how these questions are answered by
the existing framework OSGi, we will discuss them on a conceptual level. There
are two reasons for this slightly more ambitious approach:
We want to introduce terms for central concepts that are independent of existing frameworks/technologies. This is helpful because the same concept is often named differently in different frameworks. Working with frameworkindependent terms helps to compare frameworks.
We want to first focus on what has to be provided by a component framework
and focus on how it is realized only in a second step.
We first describe the conceptual implementation layers of component systems.
Then, we explain the typical concepts underlying naming and identification of
components and component implementation. Subsequently, we consider deployment and composition. Finally, we present a self-made ultra-lightweight component framework, called TCF (for Tiny Component Framework), which we developed as a first illustration of the described concepts.
4.2.1
In Section 2.2, we said that software systems consist of two layers, a platform and
the system of interest (see Figure 2.1). In CBSD with component frameworks, we
can distinguish three conceptual implementation layers (see Figure 4.1):
73
Figure 4.1:
A platform with
two containers and
deployed
components
We prefer the term container over alternative names, because the notion of containers is often used and
easily extends to settings with hierarchical deployment where a container is deployed as a component
within another container.
74
level on the other hand. For example, we can talk about JVM Version 6.0 running
on Linux as a type of a platform. We can also talk about a laptop as a specific instance of such a platform. The same is true for containers and components. At
execution time, there might be several container instances running on a given platform instance. Each container instance might contain several components and
their instances.
4.2.2
component can share code with other components. We will explain such mechanisms together with the deployment and composition concepts in the next subsection.
We assume that component frameworks support the notion of well-formedness of
components <n:v,spc,bdy>. This includes well-formedness of the name n, the
version number v, the specification spc, and the body bdy. In a well-formed component, bdy implements spc. If not stated otherwise, we will consider only wellformed components in the following.
4.2.3
75
76
4.2.4
}
In order to be registrable within a TCF container, a service in TCF has to extend
the type TCFService defined in the package tcf.framework.
A component specification is given by the interfaces and behavior descriptions
of the services it provides and the interfaces of the services it requires. A component body consists of a so-called component header and the implementation of
the provided services; that is, for each service, there has to be a class implementing the service interface. TCF requires that the body is realized within a number
of Java packages, with one of them having the same name as the component. The
header class has to implement the interface TCFComponent of the package
tcf.framework:
public interface TCFComponent {
void start(TCFContext tcx) throws TCFException;
}
The interface TCFContext is another interface defined in tcf.framework. It enables
access to the context of the component in the container. In TCF, the context is
only used to register and lookup services:
public interface TCFContext {
void registerService(String name, TCFService svc);
TCFService getService(String name);
}
The header class creates the objects implementing the services of the component
and uses the context provided by the TCF framework to register services. Assuming that the class Addr2ZipImpl implements the service Address2Zipcode, a
header class for a component zipCode implementing the service could be the following:
package zipCode;
import tcf.framework.*;
public class TCFComponentHeader
implements TCFComponent
{
public void start(TCFContext tcx) {
tcx.registerService("Address2Zipcode",
new Addr2ZipImpl());
}
}
77
78
Figure 4.2:
The components of
the phone number
example
79
package phoneNumberNA;
import tcf.framework.*;
import services.NameZipcode2PhoneNumber;
import services.Address2Zipcode;
public class TCFComponentHeader implements TCFComponent
{
public void start(TCFContext tcx) throws TCFException
{
Address2Zipcode a2z;
NameZipcode2PhoneNumber nz2p;
a2z = (Address2Zipcode)
tcx.getService("Address2Zipcode");
nz2p = (NameZipcode2PhoneNumber)
tcx.getService("NameZipcode2PhoneNumber");
if( nz2p == null || a2z == null ) {
throw new TCFException("phoneNumberNA:
services not available.");
}
tcx.registerService("NameAddress2PhoneNumber",
new NameAddr2PhoneImpl(a2z,nz2p));
}
}
class NameAddr2PhoneImpl implements NameAddress2PhoneNumber
{
Address2Zipcode a2z;
NameZipcode2PhoneNumber nz2p;
NameAddr2PhoneImpl( Address2Zipcode a2z,
NameZipcode2PhoneNumber nz2p ){
this.a2z = a2z;
this.nz2p = nz2p;
}
public String getNumber(String name, String addr) {
return nz2p.getNumber(name, a2z.getZipcode(addr) );
}
}
To start a system composed from several components, one component has to implement and register the special service TCFMain of the package tcf.framework:
public interface TCFMain extends TCFService {
void run();
}
The method run is called by the TCF container after all components are loaded
and started. In the phone number scenario of Figure 4.2, the component phoneNumberGUI implements the service TCFMain, that is, the run- method by a
class GUIPhoneNumperImpl (not shown). This class provides a simple graphical
Figure 4.3:
phoneNumberImpl
80
user interface for using the services NameZipcode2PhoneNumber and NameAddress2PhoneNumber (see Figure 4.4 for the component header and Figure 4.5 for
a possible layout of the GUI).
public class TCFComponentHeader implements TCFComponent {
NameZipcode2PhoneNumber nz2p;
NameAddress2PhoneNumber na2p;
public void start(TCFContext tcx) throws TCFException {
nz2p = (NameZipcode2PhoneNumber)
tcx.getService("NameZipcode2PhoneNumber");
na2p = (NameAddress2PhoneNumber)
tcx.getService("NameAddress2PhoneNumber");
if( nz2p == null || na2p == null ) {
throw new TCFException(
"phoneNumberGUI: services not available.");
}
tcx.registerService("TCFMain",
new GUIPhoneNumberImpl(this) );
}
Figure 4.4:
phoneNumberGUI
Figure 4.5:
A graphical user
interface for the
phone number
application
To illustrate typical rules regarding standards and formats of component frameworks, we summarize the rules that TCF components have to satisfy: A TCF component is implemented by one or more packages, one of which has the same name
as the component. The specification is given by the interfaces and descriptions of
the provided services and the interfaces of the required services. The interfaces of
the services are contained in a special package named services. The package of
the component implementation contains the header class and all dedicated implementation parts. It can import from the other packages of the component, from
tcf.framework, and from the packages of the standard Java library.
Deployment. In TCF, deployment of a component-based system consists of the
following steps, where <deployment-dir> stands for the directory from which the
TCF container loads components (see below):
81
82
(The implementation of the container can be obtained from the author of this
course.)
package phoneNumberBundled;
import tcf.framework.*;
import services.*;
public class TCFComponentHeader implements TCFComponent {
Address2Zipcode a2z;
NameZipcode2PhoneNumber nz2p;
Figure 4.6:
phoneNumberBund
led
Bundling. Bundling means constructing a new component from given components so that only the new component is visible during deployment. As an example, we bundle the components phoneNumber, zipCode, and phoneNumberNA
into the new component phoneNumberBundled. To do so in TCF, we create a new
package phoneNumberBundled with a new component header as shown in Figure
4.6. The new header starts the bundles components. The jar-file of this component
has to contain all four packages and is used during deployment like any other
component. In particular, the configuration file needs only one entry for this component. Thus, for the user of the component, it is not visible that it is a bundle of
several components.
Discussion. TCF is not a realistic component framework. It was designed to illustrate core features of component frameworks in a simple way. The reader should,
in particular, have understood and gained some intuition on the following aspects:
Syntactic rules that components have to follow
Bundling (or aggregation) of components
Deployment and configuration of systems from components
Loading and startup
Connection of component instances at runtime
Loose component coupling: A component using a service of another component is independent of the component that provides the service. In TCF, the
component providing the service is determined by the configuration, that is,
much later than the component development time.
Realistic component frameworks are more general in all or most of the above aspects. Many of the generalizations will be discussed together with the component
frameworks treated in the rest of the book. In addition, we will consider aspects
that are not illustrated by TCF, such as searching and dynamic selection of components, component distribution and load balancing, mechanisms for garbage collection, versioning, and language independency. Another important aspect is tool
support. Whereas we have presented basic realization techniques here, it should
be noted that the standardization underlying component frameworks simplifies
tool support during all stages of development.
4.3
OSGi is a component framework for Java. In particular, it supports dynamic installation and management of components. Complete applications and single
components can be remotely installed, started, stopped, updated, and uninstalled
without requiring a reboot of the host process. OSGi extends the mechanisms for
managing Java packages and classes by introducing so-called bundles as deployable units. Furthermore, it supports a life cycle model via APIs and management
policies. Bundles are registered with a service registry so that other bundles can
observe the addition of new services, or the removal of services, and adapt accordingly.
OSGi originally stood for Open Service Gateway initiative. Currently, it is marketed under the header OSGi The dynamic module system for Java (see
http://www.osgi.org). The OSGi framework is supported by a non-profit industry
alliance including a large number of top international software companies. The
alliance provides specifications, reference implementations, test suites, and certification to foster a cross-industry market and container for components. Alliance
members represent diverse markets, including SmartHome, automotive electronics, mobile and enterprise. Member company industries include leading service
and content providers, infrastructure/network operators, utilities, software developers, gateway suppliers, consumer electronics/device suppliers (wired and wireless), and research institutions. Corresponding to the alliance members, OSGi is
used in a number of different areas, including automobiles, mobile devices, and
home automation.
The OSGi framework specification has many implementations, including open
source implementations like Apache Felix (http://felix.apache.org) and Eclipse
Equinox (http://www.eclipse.org/equinox/). The examples of this chapter are developed with Eclipse Equinox, but should run with other implementations as
well. When this text was written, the current OSGi version was Release 4, Version
4.1. Even the core specification of this version has 288 pages. Thus, it is beyond
the scope of this book to explain the details of OSGi. Rather, this section has the
following goals:
83
84
4.3.1
We start the description of OSGi with aspects that are similar to TCF:
OSGi defines the structure and syntax of deployable components, so-called
bundles.
It provides a container together with a sophisticated API to install and dynamically manage components. Both together are called the OSGi framework. The framework interfaces and classes are contained in the package
org.osgi.framework .
To illustrate the explanations, we use an almost trivial service, which provides a
random number on request, and a client using this service.
Bundles. In OSGi, the units of modularization and deployment are called bundles.
Bundles can import and export packages. Similar to the TCF header class, they
can include a so-called activator class for startup. Syntactically, a bundle is
comprised of
a manifest file describing the contents of the bundle and providing information
for the installation and management of bundles,
Java interfaces and classes organized into packages,
optional data and resource files such as pictures and HTML files,
optional documentations, texts, and sources.
Bundles are stored and deployed as jar-archives. For example, the bundle of the
random number service is a jar-archive containing the following documents:
the manifest file shown in Figure 4.5,
the interface of the provided service given in Figure 4.6,
the class implementing the service given in Figure 4.7,
the activator class given in Figure 4.8.
85
Figure 4.5:
randomNumberMa
nifest
Figure 4.6:
randomNumberSer
vice
86
import randomnumberservice.RandomNumberService;
import java.util.Random;
Figure 4.7:
randomNumberSer
viceImpl
The service interface and its implementation in the Figures 4.6 and 4.7 should
need no explanations. Note that the initialization method is not available outside
the bundle and that the package java.util.Random can be imported as is from the
Java standard library.
package randomnumberservice.impl;
import java.util.Properties;
import org.osgi.framework.*;
import randomnumberservice.RandomNumberService;
public class Activator implements BundleActivator {
private ServiceRegistration reg = null;
public void start(BundleContext bctxt) throws Exception
{
System.out.println("randomNumberService starting");
// Create service object and initialize it.
RandomNumberServiceImpl rnsi =
new RandomNumberServiceImpl();
rnsi.initService();
System.out.println("randomNumberService initialized");
// register service
reg = bctxt.registerService(
RandomNumberService.class.getName(),
rnsi, new Properties());
System.out.println("randomNumberService registered");
}
The task of the activator class in OSGi corresponds to the task of the header class
in TCF. The activator has to implement a start method that
creates and initializes the component instance or instances,
looks up service references and service objects of required services,
registers service listener objects for required services if necessary, and
registers the services provided by the bundle.
And it has to implement a stop method that performs the corresponding steps for
de-registration when a bundles or service is modified or shutdown (see Section
4.3.3. for details). Start and stop methods receive the bundle context as parameter,
which enables access to the API of the framework. The activator class of the random number services (Figure 4.8) shows how to register and unregister a service
using the method registerService of the type BundleContext. Registration needs
three parameter:
1. The services type name (RandomNumberService.class.getName()).
2. The reference to the object implementing the service.
3. An object describing properties of the service (in the example, no properties
are described).
Registration returns a registration object that should be kept private by the component instance. It can be used to modify the service's properties and to unregister
the service (see method stop in Figure 4.8).
package client.impl;
import org.osgi.framework.*;
import randomnumberservice.RandomNumberService;
public class Activator implements BundleActivator {
private ServiceReference ref = null;
public void start(BundleContext bctxt) throws Exception
{
System.out.println("client : starting");
// get ServiceReference object
87
Figure 4.8:
randomNumberAct
ivator
88
ref = bctxt.getServiceReference(
"randomnumberservice.RandomNumberService");
// test if this service exists
if (ref != null) {
// get service object
RandomNumberService rns =
(RandomNumberService) bctxt.getService(ref);
System.out.println("client : got service");
// use the service
System.out.println("random number:"
+ rns.getRandomNumber());
}
}
public void stop(BundleContext bctxt) throws Exception
{
// release the service
bctxt.ungetService(ref);
System.out.println("client : service released");
System.out.println("client : stopping");
}
Figure 4.9:
randomNumberCli
ent
The use of services is illustrated by the activator class of the client (see Figure
4.9). The call of the method getServiceReference with the qualified name of the
service yields a so-called service reference. A service reference is like a handle
for a service object and encapsulates the properties and other meta-information
about its service object. The meta-information can be queried by a component to
assist in the selection of a service that best suits its needs. A service reference is
used to ask the bundle context for the corresponding service object. In Figure 4.9,
this is illustrated by the call of getService with the service reference as a parameter. By calling ungetService with the service reference, one can tell the framework
that the client object will no longer make use of the service (see the example in
the stop method of Figure 4.9). Indirect access to services via service references
also enables the OSGi framework to allow services to be of any reference type
(recall from Section 4.2.4 that in TCF, services had to be subtypes of the
tcf.framework.TCFService).
In summary, this paragraph described the structure and syntactical aspects of bundles and how they can provide and access services. The next paragraph introduces
the OSGi container.
OSGi framework. According to the OSGi terminology, the framework includes
the container, that is, the program managing the bundles, and the API for access-
89
ing the container and its services from bundles. It is comparable to the TCF container and framework API, but is much more powerful. In particular, it provides
sophisticated means to dynamically start and stop components, to listen to modifications of the connections between components and services, and to manage different versions of code. Here, we focus on the basic mechanisms for installing,
starting, stopping, and uninstalling bundles.
Figure 4.10:
The states and
transitions of
bundles
Figure 4.10 depicts the states of a bundle within an OSGi container and possible
transitions2:
INSTALLED The bundle has been successfully installed in the container.
RESOLVED All Java classes that the bundle needs are available. This state
indicates that the bundle is either ready to be started or has stopped.
STARTING The bundle is being started, the BundleActivator.start method
will be called, and this method has not yet returned. When the bundle has a
lazy activation policy (specified in the Manifest), the bundle will remain in the
STARTING state until the bundle is first used. This potentially saves resources and initialization time.
ACTIVE The bundle has been successfully activated and is running; its
Bundle Activator start method has been called and returned.
The state transition diagram and description follow the explanation in Section 4.3.2 of the
OSGi specification.
90
Figure 4.11:
OSGiSession
$ equinox
osgi> install file:///osgi/plugins/randomNumberService.jar
Bundle id is 2
osgi> install file:///osgi/plugins/client.jar
Bundle id is 3
osgi> start 2
randomNumberService starting
randomNumberService initialized
randomNumberService registered
osgi> start 3
client : starting
client : got service
client : generating a random number: -1065829765
osgi> stop 3
client : service no longer in use client : stopping
client : stopping
osgi> uninstall 3
osgi> close
randomNumberService stopping
The command interpreter allows controlling the container and the bundles and
displaying status information and profiling information. Essentially, it provides a
command line interface to the OSGi framework API. That is, the explicit actions
that can be performed using the command interpreter can also be executed from
programs. In particular, bundles can install and start other bundles, and processes
running remotely can control a container.
An important aspect of the OSGi container is that it stores its current state in a
persistent representation when the container is closed. If the container is relaunched, it sets up the state it was in before closing. For example, the close operation at the end of the transcript in Figure 4.11 stops the random number service, but records that the service was running. On relaunch of the container, the
service is automatically restarted. This feature provides a lot of flexibility to compose and connect bundles. On the one hand, the container can be used to resolve
the static dependencies and define a startup order, i.e., almost like a classical
linker and startup tool. On the other hand, all integration and connection operations can be done at a systems runtime; in particular, bundles can dynamically be
updated to new versions.
The following two subsections will describe more details on how bundles and
packages are related and how dynamic service management can be realized.
4.3.3
In addition to requiring and providing services, OSGi bundles structure the program code of component-based systems. Program code is managed in the form of
Java packages. OSGi supports a number of quite complex mechanisms and rules
for package export and import, package identification, class loading and privacy
issues, as well as for handling package versions and version constraints. Here, we
concentrate on the central aspects and illustrate them with a vendor/consumer scenario.
Exporting packages vs. providing services. The bundle randomNumberService
includes two packages. The package randomnumberservice is exported so
that other bundles can import the type of RandomNumberService and use the
bundles service. The implementation package randomnumberservice.impl remains hidden in the bundle. The bundle is an example of a component that exports some program code (namely the interface type of the services)
and provides some implemented service. In this example, the static aspect of the
bundle as a deployable code unit and the dynamic aspect as provider of a service
at runtime fit well.
In general, the relationship between code and provided services is more complex
(cf. also the discussion in Subsection 2.3.4). As a first example, let us consider a
service that should be provided by different components: Into which bundle
should we place the interface type of the service? And if we put it into all bundles,
91
92
how can we make sure that a client of several of these bundles considers all these
types to be the same (that means in Java that they have to be loaded with the same
class loader)? Another issue is code sharing. If several components use the same
code parts (and the functionality of the code cannot or is better not wrapped into
services), it would be a waste of storage resources and dangerous for code maintenance to include a copy of the code in every bundle.
As a solution, the static and dynamic aspects of bundles are decoupled to some
extent. A bundle can provide services without exporting packages. It can export
packages without providing services. It can do both or neither. As this relationship
between static and dynamic aspect is of general interest in CBSD, we illustrate it
with a small example.
Figure 4.12:
A market scenario
to illustrate static
and dynamic
bundle relations
Vendor/consumer scenario. Figure 4.12 shows several vendors (VendorA, VendorB, etc.) that provide a simple service to possible consumers: A consumer can
ask a vendor to make an offer for a certain number of products. The vendor answers with the price or with -1 if no offer is made. The consumer can also ask an
agent to make an offer. The agent tries to determine the best price from among all
vendors. It is clear from Figure 4.12 that vendors provide a service, that agents
require and provide services, and that consumers only require services. Figure
4.12 also shows the export and import relation of packages. Package export is depicted by an upturned square. The bundle Market exports the package market,
the bundle Agent exports the package agent. Package import is depicted by a
dashed arrow. For example, all bundles import the package market. Thus, the two
vendor bundles are examples of components that provide a service, but no code.
Agent is a component providing a service and exporting code. Market only exports code. And the consumers neither provide a service nor export code.
93
package market;
public interface Vendor {
int makeOffer( Product type, int amount );
// result = -1 : no offer is made
// result > 0 : offered price in Cent
}
public enum Product {
BEER, WHEATBEER, PILS
}
Figure 4.13:
packageMarket
94
}
}
package agent.impl;
import agent.Agent;
import market.Vendor;
import java.util.*;
import org.osgi.framework.*;
public class Activator implements BundleActivator {
private BundleContext bctx;
private ServiceRegistration reg;
private AgentImpl agt;
public void start(BundleContext bc) throws Exception {
bctx = bc;
Set<Vendor> vrefs = new HashSet<Vendor>();
try {
ServiceReference[] refArray =
bctx.getAllServiceReferences(
Vendor.class.getName(), null);
// extract the non-agent vendor services
Vendor v;
for (ServiceReference ref : refArray) {
v = (Vendor) bctx.getService(ref);
if (!(v instanceof Agent)) {
vrefs.add(v);
}
}
} catch (InvalidSyntaxException e) {
// never reached because filter is always null.
}
agt = new AgentImpl(vrefs);
reg = bctx.registerService(Agent.class.getName(),
agt, new Hashtable());
95
To learn more about some technical aspects of OSGi, it is worthwhile studying the
implementation of the activator shown in Figure 4.15. The OSGi framework provides a method getAllServiceReferences that yields all registered services implementing a certain interface (here market.Vendor). This method can
take a so-called filter as a second argument to select only services with certain
properties. In the example, no filter is given which is the same as a filter that always returns true. The try-catch block catches syntax errors of filter expressions
that might be thrown by the method getAllServiceReferences (not possible in the scenario because of the missing filter). The activator extracts all nonagent vendors from the returned array of service references and passes the set of
vendors to the created agent, which is finally registered at the bundle context, i.e.,
at the container. The following aspects and design decisions should be noted:
The determination of the vendor set happens at agent startup. This only makes
sense if vendors register before the agent and if the set of vendors does not
change over time. In a more dynamic scenario in which vendors come and go,
other techniques are needed (see Section 4.3.3).
The agent gets a set of service objects, not a set of service references (see Section 4.3.1 at the end of the paragraph Bundles for the difference). The advantage is that the agents access to the vendors is faster. The disadvantage is
that property changes of vendors cannot be observed and that the agent cannot
notify the container when a vendor service is no longer needed.
Checking that vendors are not agents themselves is not necessary (and using
the instance of operator for it is bad code and can only be justified by its brevity). Agents could use other agents as well. However, one has to be very careful of not creating cycles. In the given scenario, cycles are not possible. In the
more dynamic scenario of Section 4.3.3, they are possible and would lead to
non-termination of the method makeOffer. It should be noted that such cycles are a general problem in dynamically changing service structures.
Finally, we would like to remark that putting the interface type Agent into the
bundle Agent is only a reasonable design if we are sure that even in the future our
system will not get further agent components. (Our rationale here was to show a
bundle that exports a package and provides a service.)
Summary and beyond. Components in general and OSGi bundles in particular
have two functions: they provide services and manage code. Combining these two
Figure 4.15:
classActivator
96
4.3.3
OSGi provides a very dynamic runtime environment. At runtime that is, when
the OSGi container/framework is running ,
bundles can be installed and resolved, or uninstalled (corresponding to dynamic linking and unlinking of modules);
resolved bundles can be started and stopped (corresponding to loading and
unloading of executables);
services can be registered and unregistered (corresponding to passing pointers
between loaded modules in imperative programming).
OSGi supports a sophisticated infrastructure that enables a bundle to observe
changes in all these categories. Bundle events report on changes in the life cycle
of bundles, service events allow observing services, and framework events cover
aspects like framework startup, framework shutdown, refreshment of packages,
and occurred errors. Bundles can register so-called listener objects for certain
kinds of events. If an event of that kind occurs, the listener object is notified by a
call of a method of the listener interface.
A detailed explanation of OSGi's event infrastructure is beyond the scope of this
textbook. However, since dynamic reconfiguration and plug-in mechanisms are
important applications of component frameworks, we cannot do without considering the basic technique with an example.
Using events. In the vendor/consumer scenario of Section 4.3.2, an agent computed the best offer of a set of vendors. If we generalize this scenario into a system in which vendors can join and leave the market place (like in a Web environment), the agent has to observe these changes. To do so, we register a service listener object with the bundle context. Such an object implements the interface:
public interface ServiceListener
extends java.util.EventListener {
97
The definition of the service listener and its registration with the bundle context is
depicted in Figure 4.16. It has to be inserted into the activator of Figure 4.15 at the
end of the method start (a comment indicates the insertion point). Thus, we
Figure 4.16:
serviceListener
98
create and register the listener directly after the registration of the service of the
agent. The slight chance that we miss a service event in between the computation
of the vendor set and the registration of the listener is neglected here for the sake
of simplicity. Usually, the service listener is an object of an anonymous class. The
method serviceChanged acquires the service object that caused the event,
checks whether it is a non-agent vendor and, if so, reacts to the change.
Closing comment. The event infrastructure is a powerful high-level mechanism
for coping with all kinds of dynamic changes. However, the complexity of this
mechanism should not be underestimated. In particular in concurrent systems,
managing updates of the service structure and keeping track of references used to
avoid memory leaks is a challenging and error-prone development task.
4.3.4
In the subsections above, we have covered central aspects of OSGi, namely the
notion, structure, and life cycle of bundles (Section 4.3.1), the organization of
program code into bundles (Section 4.3.2), and the use of events to observe
changes in OSGi containers (Section 4.3.3). The OSGi framework is more sophisticated in these aspects and provides further features. To illustrate this sophistication, we will briefly discuss some of those issues:
Security: OSGi supports an optional security layer to authenticate bundle
code. It is based on the Java 2 security architecture and provides the infrastructure for deploying and managing applications that must run in finegrained controlled environments.
Versioning: OSGi allows the use of use different versions of a bundle at the
same time. Similarly, it supports the use of different package versions. Import
declarations can declare a precise version for the resolution process. Alternatively, they can declare a version range for matching possible export definitions. OSGi provides a version-matching mechanism as part of the package resolver to handle such constraints.
Container startup and shutdown: OSGi defines a precise procedure for
starting up and shutting down a container (in OSGi terminology, startup and
shutdown of the framework). Between shutdown and startup, it keeps the information about installed and resolved bundles and records all bundles that
were started at shutdown time. Started bundles at shutdown are automatically
restarted on container startup. In addition, all relevant events informing components about shutdown and startup are delivered.
Final remarks. OSGi can be considered as a prototypical language-specific and
application-unspecific component framework. It is based on a detailed notion of
bundles as deployable components. The component model is described well on a
syntactical level. However, the framework provides no support or guideline for
handling concurrency and synchronization issues. This can be interpreted as flexibility for developers who understand all components of the system to be built. It is
a weakness for component developers who do not know the execution contexts in
which their components will be used.
The mechanisms for installing, linking, and starting components and their versions are quite sophisticated in OSGi. On the other hand, OSGi provides only
weak support for selecting and connecting components. Each component has to
manage the connections to required services by itself. In particular, there is no
support for automatically resolving service dependencies.
4.4
99
100
written in different programming languages. Multi-language support can be achieved in different ways:
Common IDL: Some component frameworks use a common interface description language, IDL, to express component interfaces in a programming
language independent way. For example, the CORBA Component Model,
CCM, uses the CORBA IDL. So-called language mappings bridge the gap between programming language-specific interfaces and the IDL. More details on
how this mechanism works will be given in Chapter 5.
Common platform: A typical example of a multi-language framework with a
common platform is Mircrosofts .NET framework. It achieves the integration
of components written in different language by providing the so-called common intermediate language, CIL, and the common language runtime,
CLR, for executing CIL code. The CLR also supports a powerful common library. There are compilers for different languages using CIL as the target language.
Common binary standards: Integration can also be done on the machine code
level by defining appropriate standards for executable components and tools
for linking and loading such binary components. This solution allows for high
efficiency, but is usually quite dependent on the underlying computing platform. For example, COM components use this technique.
Multi-language support emphasizes the fact that CBSD covers many aspects that
go beyond programming.
Distribution. In a distributed component system, the components can run in different processes, on different computers, at different sites, under the responsibility
of different organizations. Distribution can be used to increase performance by
deploying the components of a system to several computers. In such scenarios,
distribution is often supported by a load balancing mechanism that starts component instances on those computers with the most resources available. Load balancing can also move running component instances from one computer to another.
Several component frameworks provide support for load balancing (e.g., COM+
from Microsoft).
Distribution is unavoidable if remote partners want to communicate within a
component-based system. For example in the retail store example of Section 1.3.,
some components run on the CashDeskPlatform, while others run on the StoreServer. Distributed component systems need not be under the control of one container, have to handle remote service references, and should organize the data
transport between sites. We will consider these aspects together with the CORBA
in Chapter 5.
Automated connecting of components. In OSGi, the bundle activator usually
establishes the connections of the component instance to its required services (see
101
4.5
Problem 4.1:
Problem 4.1:
Two components
Problem 4.2:
Explain the main composition mechanisms of component frameworks.
Problem 4.3:
A bundle can be in different states in the OSGI container. What corresponds to the
transition from INSTALLED to RESOLVED in traditional software construction?
Problem 4.2:
Component
frameworks
Problem 4.3:
States of a bundle
102
Problem 4.4:
Static and dynamic
aspects of bundles
Problem 4.5:
Services
Problem 4.4:
What are the static and dynamic aspects of bundles? How are they related?
Problem 4.5:
Components provide and use services. We say that component C1 depends on C2
if C1 uses a service provided by C2. How can this dependency relation be cyclic?
Can a component observe whether it is part of a cycle? What are the problems of
cyclic dependencies?
5.1
5.2
In this section, we present and discuss the general problems and concepts related
to the development of (distributed component-based systems). The basic picture
is that there are two or more components running in different operating system
processes on one or several computers. Such scenarios can be found in embedded
systems like cars or airplanes, in application-specific communication systems, as
well as in information systems.
We assume here that the communication infrastructure between the processes abstracts from communication faults in the following way: Either the communication works correctly or the communication partners are notified that an error has
occurred. That is, error detection and mechanisms for fault tolerance are considered to be part of the infrastructure. In general, the infrastructure can support different kinds of communication:
Remote procedure call (RPC)
103
104
Figure 5.1:
A remote object S
registered at some
registration process
Figure 5.2:
A client object
connected to a
remote object
Concepts of RMI. Figures 5.1 and 5.2 illustrate how RMI works. Processes providing services have to register their services. In the depicted scenario of Figure
5.1, the server process has registered the remote object S at the registry. Object S
provides a service with the name aService. Such an object is sometimes called a
servant (for example in CORBA). In order to perform the registration, the RMI
framework has to provide a mechanism for registering a remote reference under a
name. In a Java environment, the registration mechanism and the handling of remote object references is directly managed by the Java Virtual Machine. In
CORBA, the registration mechanism is provided by the naming service of the socalled object request broker; object references are exported as so-called Interoperable Object References (IOR). On the server side, the servant is represented by a
so-called skeleton.
If a client wants to use a remote object or service, it has to look up the corresponding object reference under a name at the registry. To do so, the client object talks
to the lookup mechanism of the RMI framework in its own process and receives a
process-local reference to a so-called stub. The stub represents the remote object
within the client process. In particular, it provides the same methods as the public
interface of the servant. The client can invoke methods on the stub and pass the
stub reference to other objects.
To transfer arguments between stub and skeleton, objects and values have to be
handled in a process-independent form. In Java, for example, local objects are serialized and remote objects are coded in a special way. Transforming arguments
into a process-independent representation is called marshaling. The reverse transformation is called unmarshaling. In RMI, the stub marshals the parameters and
transfers them to the skeleton. The skeleton unmarshals the parameters, calls the
method on the servant, accepts the result, marshals the result, and sends it back to
the stub. Finally, the stub returns the unmarshaled result to the client object.
RMI has some similarities with service registration in OSGi (cf. Section 4.3). As
in local settings, client components do not need to know the components providing the service. However, the distribution of the registration and lookup mechanism to several processes has a number of technical and even semantic implications:
Handling of remote references
Passing of parameters and results
Method binding and type checking
Managing program code
Handling of errors
We will explain and discuss these conceptual aspects in the following paragraphs.
They are the starting point for understanding, developing, and using RMI. As
105
106
most component frameworks build on object orientation and RMI for distribution,
the concepts of RMI are also essential for CBSD.
Handling of remote references. Process-local object references can be represented by a memory address or some identifier derived from addresses. Outside
the process, such a representation is
not sufficient, because references in two different processes might be represented by the same address;
not practical, because once an address is passed out to a remote process, objects can no longer be moved in the (local) address space, which is too restrictive for most runtime systems with garbage collection.
To be used from remote sites, the representation of an object reference has to
identify the object within its process, the process on its machine, and the machine
within the Internet or some other name space. Based on such a representation, a
remote reference can be passed around from process to process. For example, let
us assume that the client process in Figure 5.2 has a reference to another remote
servant T. It could pass the reference of S as a method parameter to T when invoking a method on T. This way, an object can receive references to objects in
many other processes.
Frameworks for distributed object-oriented computing or distributed component
systems differ in the way they represent remote references and in the operations
and forms of equality they support for remote references.
Passing of parameters and result. Passing primitive data values, like integers,
characters and Booleans, as parameters or result is only a coding problem. Design
decisions have to be made when objects are allowed as method parameters (the
same is true for objects as result). Essentially, there are two options:
1. Pass objects by reference, that is, create a remote reference for the parameter
object and pass it to the servant.
2. Pass objects by copy, that is, serialize the parameter object and all objects directly or indirectly referenced by it and create a copy of the parameter object
and the object structure referenced by it within the process of the servant.
The main disadvantage of calls by reference is that every method call of the servant on a parameter, even on a string object or an array, becomes a remote call.
This is very expensive. The main disadvantages of the second option are that potentially large object structures have to be copied (which is expensive) and that
object identity on the parameter objects is broken: If the servant modifies the parameter objects, the original copies within the client process remain unchanged.
Thus, the second option has semantics that are slightly different from local
method invocation semantics. Most frameworks allow the component developer
to combine both options in order to achieve good performance.
Method binding and type checking. As we have seen in Section 4.3, OSGi containers perform type resolution at deployment time. When a new bundle is installed, the container figures out which types of the new bundle are the same as
types already available in the container and which are different. In a distributed
scenario, type resolution and compatibility checking become part of the lookup
and the invocation mechanisms. In many frameworks, the server side dynamically
checks the types of the parameters and the client side checks the type of the result.
Managing program code. Object-oriented distributed programming and component development have to deal with another non-trivial problem that is best explained with an example. Let us consider a servant providing the following simple
service interface:
public interface CheckerService {
public boolean check( Offer o );
}
The method checks whether an offer has a certain property. It does so by calling
methods on the Offer object. We assume here that Offer is an interface type.
When a remote client uses the service, the parameter should be copied and created
on the servant side. But what happens if the classes needed to deserialize the
passed parameter are not available at the servant side? The implementation of the
checker service can be compiled based on the interface type; it does not need access to classes implementing the type Offer (and even if the servant knows some implementations of Offer, some future client may pass an Offer object that
is an instance of a class that is unknown to the checker service implementation).
To overcome this problem, a framework for distributed object-oriented component development needs support for loading new classes and components at runtime from remote sides, that is, for dynamic automated deployment. Without going into details, it should be clear that such mechanisms can become quite complex and may cause difficult security problems.
Handling of errors. All of the above conceptual distribution aspects are a source
of errors and exceptions. In addition, there are errors and exceptions that are
caused by the underlying communication infrastructure. Whereas in an ideal scenario, developers might tend to treat distribution issues as transparently as possible, the realistic approach taken by most of the frameworks today is to expose the
component, application, and system developer to the errors and exceptions that
might be caused by distribution. Thus, although a remote method invocation is in
many respects like a local method invocation, and although the communication
infrastructure abstracts from many low-level aspects, development of distributed
systems still needs additional knowledge and care.
Further aspects. The goal of this section was to present the basic conceptual aspects of distributed object-oriented computing before explaining how they are
handled by particular frameworks and technologies. Beside these basic aspects,
107
108
5.3
Distribution and integration of components of different development organizations cause another challenge. The computing platforms underlying a distributed
system can be heterogeneous in terms of the hardware, the operating system, and
other platform aspects. Furthermore, different developers might use different programming languages for implementing components. We distinguish two different
forms of how heterogeneity is solved:
1. Direct composition, i.e., heterogeneous components can be directly linked
and connected.
2. Brokered composition, i.e., heterogeneous components are linked to a broker
that mediates between components.
To handle direct composition, we need standards that allow linking and loading
compiled components written in different languages. These standards can be defined on the level of machine or assembler code, or they can be established on the
level of virtual machine code (as in Microsofts .NET framework; cf. Section 4.4).
Technology for direct composition has to enable the linking of component code
and the connection of component instances. In particular, a component written in
one language can directly call a method/function of a component written in another language. The main advantage of direct composition is that it is more efficient than brokered composition. The main disadvantage is that components can
easily corrupt other components.
In brokered composition of heterogeneous components, the communication between components is mediated by an intermediate layer. A component written in
one language talks to its broker. The broker manages the communication with other components. In particular, the broker can convert different representations of
data types and handle references between components. Brokers can connect components within one process or components distributed across different platforms.
109
module ExampleModule {
enum Product { BEER, WHEATBEER, PILS };
interface Vendor {
long makeOffer( in Product type, in long amount );
};
};
Whereas direct composition has to define the standards for naming, data types,
references, method calls, and communication on the level of (virtual) machine
code, brokered composition can base the integration on a high-level interface
definition language or IDL. For example, Figure 5.3 shows a definition of the
interface between vendor and consumer components according to the vendor/consumer scenario of Section 4.3.2 in the CORBA IDL (compare to the Java
version in Figure 4.13). As the syntax is very similar to Java, the definition should
be clear; the keyword "in" in front of parameters indicates that the parameter is
an input to the method (CORBA IDL also supports output parameters).
To integrate different programming languages (e.g., C and Java), frameworks
based on brokered composition realize so-called IDL mappings for each of these
languages. An IDL mapping for a language L specifies how an interface defined
in IDL corresponds to an implementation defined in L. Usually, a mapping for a
language L comes with a compiler that generates declarations in L from interface
definitions in IDL. The component developer uses these language-specific interfaces to implement components in L.
More aspects of brokered composition will be explained together with the introduction to CORBA and the CORBA Component Model in the following section.
5.4
Figure 5.3:
moduleMarket
110
As described in [29], Section 2.7, CORBA is used in everything from billing systems and multi-media news delivery to airport runway illumination, aircraft radio
control and the Hubble space telescope. Most of the worlds telephone systems, as
well as the truly mission-critical systems operated by the worlds biggest banks,
are built on CORBA. Applications and success stories of CORBA can be found,
for example, at www.corba.org.
This section explains the main parts of CORBA and focuses in particular on language independency and the object broker infrastructure. We first sketch the basics of the architecture (Section 5.4.1), then demonstrate the development of a
simple multi-language distributed system with CORBA (Section 5.4.2), summarize further aspects of CORBA (Section 5.4.3), and explain the CCM (Section
5.4.4).
5.4.1
Figure 5.4:
The simplified
runtime view of
CORBA
Figure 5.4 gives an overview of the Common ORB Architecture. To simplify the
wording in the following explanations, we call the process of the client objects the
client process and the process of the servants the server process, respectively.
However, it is important to understand that in general, a process can contain client
objects and servants at the same time and that its client objects can communicate
with remote servants and the servants can provide services for remote clients.
CORBA concepts. The Object Request Broker, ORB for short, together with its
services provides the functionality of a distributed container infrastructure. Technically, the ORB is often realized as library code that is linked to the client and
server processes. It is accessed by clients and servants through a large API. (In
some respects, the ORB corresponds to the TCF or OSGi frameworks described in
Chapter 4.)
Compared to RMI, the relationship between clients and servants is more refined in
CORBA. In RMI, a remote reference directly corresponds to a servant object. In
CORBA, there is an additional indirection layer between the client and the servant. The indirection is handled by the so-called object adapter (see Figure 5.4).
The reason for this indirection is to increase the flexibility and power of the
framework. In CORBA, it is possible, for example, that a server process is
stopped after it has registered a service at a registry process. This is important in
order to achieve scalable performance in large systems. If a client wants to use the
service, it can look up the service at the registry, obtain a reference R, and invoke
a method on R. Note that at this point, the server process is still down and there is
no object that is referred to by R. If configured appropriately, the method invocation on R leads to an automatic restart of the server process. A new servant object
is created and made available to the client. That is, the reference stored in the registry only establishes an indirect, dynamic correspondence to servant objects.
To achieve its flexibility, CORBA provides so-called Interoperable Object References, IORs for short. An IOR is a reference to a so-called CORBA object. IORs
can be stored in files or registries, can be compared, passed around among processes, and used to invoke methods. However, as sketched in the scenario above,
the referenced CORBA object does not correspond to a "physical" servant object
in a running process. It can be represented by different servant objects (over time,
but as well at the same time). A CORBA object is called transient if it has a
shorter lifetime than the server process. Otherwise, it is called persistent.
The so-called object adapters manage the relationship between IORs and servants/skeleton. Object adapters provide a lot of flexibility. In particular, an object
adapter can manage several servants. In the first versions of CORBA, the socalled basic object adapters were only vaguely described by the standard. Newer
CORBA versions standardize so-called portable object adapters, POAs, for short.
In the following, we only consider POAs. The object adapters support different
policies for transient and persistent objects.
To realize mechanisms for shutdown and startup of server processes, CORBA
supports so-called implementation repositories, IMRs. As indicated in Figure
5.4, an IMR runs in a separate process and can manage many server processes. If
an IOR R refers to a CORBA object in an IMR-managed server and this server is
111
112
down, an invocation on R triggers the IMR to start the server and to redirect the
IOR communication to an appropriate, newly created object adapter.
Concepts for multi-language support. CORBA server and clients can be implemented in different languages. To achieve this, CORBA standardizes a common, programming language independent IDL to describe the interfaces between
clients and servers and language mappings to C, C++, Java, Ada, Smalltalk, COBOL, PL/I, LISP, Python and IDLScript; further non-standardized language mappings exist. To support some programming language PL, a CORBA implementation has to provide:
an implementation of the CORBA framework in PL and
a compiler from IDL to PL.
Figure 5.5:
A multi-language
compilation
scenario with IDL
5.4.2
113
Figure 5.6:
The processes in
the market scenario
The first aspect that should be noted in the market scenario of Figure 5.6 is that
the client-server relationships among the processes are more interesting than in
the basic configuration we have discussed so far. Vendors are clients to the market
when they register via registerVendor. The market is a server for both the
vendors and the consumer (getAllVendors). The vendors are servers for the
consumer when the consumer asks them to make on offer.
module MarketIF {
enum Product { BEER, WHEATBEER, PILS };
interface Vendor {
long makeOffer( in Product type, in long amount );
};
typedef sequence<Vendor> VendorSeq;
interface Market {
114
Figure 5.7:
moduleMarketServ
er
The interfaces and types for the market scenario are defined in the IDL module
shown in Figure 5.7. IDL can declare interface types similar to Java interfaces and
several other kinds of types, like enumerations and collection types. From the
module MarketIF, the IDL-to-Java compiler that we used3 generates packages
with 19 class and interface declarations. The IDL-to-C++ compiler that we used4
generates a 790-line C++ file and a 275-line header file.
As it is beyond the scope of this book to explain the details of the language mappings, we only describe those aspects that are needed to develop simple CORBAbased systems. In the following, we explain how to implement clients (using the
consumer as an example) and servers (using the market as an example). We
briefly discuss implementations in different languages (using vendors as an example) and finish with some remarks about deployment and startup.
import
import
import
import
MarketIF.*;
// imports the IDL generated types
org.omg.CosNaming.*;
org.omg.CosNaming.NamingContextPackage.*;
org.omg.CORBA.*;
115
Client development. In the client, the main difference between a local and a distributed scenario is the access of the remote reference. In the case of the consumer, it is the problem to get the IORs of the market and of the vendors. Figure
5.8 shows the needed steps:
1. First, we have to initialize the ORB.
2. From the ORB, we can get a reference to a name service, that is, to a remote
object that maps names to IORs. The reference returned by the method resolve_initial_references has to be cast down to the type NamingContext. These downcasts are realized by methods called narrow, which are
defined in appropriate helper classes.
3. The naming context provides a method resolve returning the IOR stored
under a name: Names may consist of several components; that is why they are
represented by arrays.
4. The reference returned by the method resolve has to be narrowed to the
appropriate type. In our case, this is the type Market. Note that the types Market and MarketHelper are generated from the IDL module, whereas the other
types mentioned so far are part of the CORBA framework.
The reference of the type Market can be used as if it were a reference to a local
object. For example, it can be used to call the method getAllVendors. This
method returns a vendor array because the IDL sequence type is mapped to Java
arrays. It is important to note here that the vendor references stored in the array
are IORs and that these IORs may refer to objects in processes different from the
process of the client (i.e., the consumer process) and server (i.e., the market process ).
import MarketIF.*;
import java.util.*;
class MarketImpl extends MarketPOA {
private Set<Vendor> vendors = new HashSet<Vendor>();
public void registerVendor(Vendor v) {
vendors.add(v);
}
Figure 5.8:
consumerJava
116
Figure 5.9:
marketJava
Server development. The development of a server consists of the servant implementation and of a program that creates the servant and makes it accessible to other processes. We illustrate the steps with an implementation for the market server.
Whereas the stub class generated for an IDL interface MyService has the same
name, the generated skeleton class is an abstract class named MyServicePOA (in
the Java language mapping). The name is definitely misleading, as objects of such
classes are not portable object adapters (POA). A class implementing servants is
typically realized as a subclass of the skeleton class. It is usually called MyServiceImpl. In the Java language mapping, neither MyServicePOA nor MyServiceImpl have to be a subtype of MyService; however, MyService has to implement all methods of the interface MyService. Figure 5.9 demonstrates the servant
implementation for Market.
import
import
import
import
import
MarketIF.*;
java.util.*;
org.omg.CosNaming.*;
org.omg.CosNaming.NamingContextPackage.*;
org.omg.CORBA.*;
117
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
// Bind the object reference in naming context
NameComponent[] p = {new NameComponent("Market","")};
ncRef.rebind( p, marketRef );
// ORB waits for incoming requests
orb.run();
} catch(Exception e) { ... }
}
}
The steps needed to create and register an IOR for a servant are illustrated by the
class MarketServer in Figure 5.10. The steps shown are:
1. Create and initialize the ORB.
2. Create and activate a portable object adapter.
3. Register a servant object with the POA. The shown operation servant_to_reference returns the IOR for the servant.
4. Get the (root) naming context of the ORB.
5. Bind the IOR of the servant to an appropriate name. The method rebind is
used to overwrite an existing binding with the same name.
6. Start the ORB loop that listens to incoming requests.
Of course, CORBA provides many other options and variants to start up processes
participating in distributed systems. For example, a vendor server does not register its vendor(s) with a naming service, but registers them without a name at the
market (or even at several markets).
import MarketIF.*;
class Vendor1Impl extends VendorPOA
{
public int makeOffer( Product type, int amount ) {
if( (type==Product.PILS) && (50 >= amount) ) {
return 200 * amount;
} else {
return -1;
Figure 5.10:
marketServerJava
118
}
}
Figure 5.11:
vendorServerJava
}
#include <CORBA.h>
#include "MarketIF.h"
using namespace CORBA;
using namespace MarketIF;
class VendorImpl : virtual public POA_MarketIF::Vendor
{
public:
virtual Long makeOffer(Product type, Long amount );
};
Figure 5.12:
vendorCPP
Aspects of language mappings. The language mappings to object-oriented programming languages are quite similar. Figures 5.11 and 5.12 illustrate the similarities with a Java and C++ implementation for vendor servants. The main differences are how the IDL types are mapped to the programming language types and
how the module structure of the CORBA framework is expressed by means of the
programming language constructs. For target languages without support for objects, the mappings are more involved.
Deployment and startup. CORBA does not provide standardized mechanisms
for the deployment of client or server code. The technical details of deployment
depend on the CORBA implementation used, on the underlying programming environments, the platforms, and the communication protocols of the used networks.
Here, we briefly describe the general aspects shared by all CORBA implementations and platforms.
The deployment of clients consists of the client code itself and the library part of
CORBA (the development part of CORBA, in particular the IDL compiler, does
not need to be available). Furthermore, appropriate configuration and log files
could be provided that configure parameters of CORBA to yield diagnostic information. When starting a client process, one should make sure that the server
5.4.3
CORBA standardizes the core mechanisms presented above and the needed languages and formats. This includes the IDL, which is much more elaborate than
demonstrated. In particular, it provides many additional forms of type declarations, such as so-called value objects, and support for versioning. The CORBA
standard defines the formats for IORs, the CORBA URL formats, details on marshaling parameters, and aspects of communication protocols. In addition to this, it
describes a number of infrastructure features and services supported by the
CORBA framework. As an overview, we list the more important features and services in the following and discuss those that are particularly relevant for CBSD.
The CORBA infrastructure provides portable interceptors to be called at creation
time of IORs and whenever a request is made on an IOR. Interceptors can be
used, for example, to log profile data for an IOR. Furthermore, the CORBA infrastructure supports mechanisms for fault tolerance, reflection, and specific forms of
message handling (the latter two mechanisms are discussed below). In addition to
119
120
The Asynchronous Messaging Interface (AMI) enables non-blocking invocations. To retrieve the results of a non-blocking invocation, AMI supports callback
objects. The caller of a non-blocking invocation can continue its execution after
the invocation and later retrieve the results from the callback object. (What is
called a callback object in CORBA is similar to what is often called a future in
the programming literature.)
CORBA also provides an infrastructure for so-called Time Independent Invocations (TII). If a client is down when a method call returns, TII keeps the reply
message in some form of persistent storage until the client is restarted. Then, the
reply message is delivered. The TII mechanism is built on so-called method
routers, which ensure that the message is stored persistently until it can be delivered.
Trading service. CORBA does not prescribe how a CORBA object is made accessible to clients. It provides several options for how this can be done. If client
and server have access to the same file system, a server can simply store the IOR
in a file from which the client reads the IOR. In Section 5.4.2, we have illustrated
how a CORBA object can be made accessible using CORBA's Naming Service.
The server registers the IOR at the naming service by binding it to a name. Clients
can look up the IOR under the name. The market object in the market scenario of
Section 5.4.2 demonstrated a (trivial) programmer-defined service for making the
CORBA vendor objects accessible to consumers.
Through its trading service, CORBA provides a sophisticated and powerful
mechanism for advertising services by its properties. More precisely, the trading
service builds on:
The ServiceTypeRepository, a repository of service offer types: A service offer type describes the properties of a class of services, for example all printing
services on postscript printers. It is given by a name, a CORBA (super-)type
of the service to be offered, and properties that the offered service must have.
The repository stores such service offer type descriptions and allows adding
new service offer types and removing existing ones.
The Register mechanism for registering a service with certain properties for a
service offer type T. A service may only be registered for T if it has at least
the properties of T. It is import to understand that many services can be registered for the same service offer type T. (This is one main difference to the
naming service that associates a name with at most one service.)
A Lookup mechanism for finding the service that is the best match for a specified constraint. Constraints are formulated as Boolean expressions over the
properties defined in the service offer type. CORBA provides a special language for these expressions, the so-called Trader Constraint Language
(TCL).
121
122
The CORBA trading service also provides mechanisms for joining several Trading Services so that a query to one trading service can be propagated to other services. From a conceptual point of view, the trading service infrastructure is interesting, as it illustrates the basic features needed for CBSD scenarios with automated component selection based on component properties.
5.4.4
CORBA is a framework for the development of distributed object-oriented systems. Up to Version 3.0, CORBA did not support a component model and the deployment of components. Starting with Version 3.0, CORBA was complemented
with a component model, the so-called CORBA Component Model, CCM for
short. CCM was originally part of CORBA 3.0 and is now a separate specification
(version numbers start with 4.0). CCM has a number of similarities with component frameworks for application servers. However, by supporting language and
platform independency, it goes beyond many of these frameworks. The shared
conceptional aspects of component frameworks for application servers will be
presented together with Enterprise Java Beeans in Chapter 6. Here, we briefly
sketch aspects specific to CCM.
Figure 5.13:
CCM component
with ports and
attributes
123
components. Events are a mechanism for asynchronous communication. Attributes are named configurable component properties. They are intended to be configured at assembly, packaging, deployment, or instantiation time. In addition to
these interfaces, a component has a home interface for component instantiation
(for an explanation of home interfaces, see Chapter 6).
CCM supports four different component categories. Service Components are
stateless components that are used to model sessions. They do not provide a key
for persistent access. Session Components have a state, but no key. Process
Components have a persistent state that is not visible to clients, a persistent identity, and a (potentially transactional) behavior. Entity Components have a persistent state that is visible to clients through a primary key and a (potentially transactional) behavior (for an explanation of home interfaces, we again refer to Chapter
6).
module producerScenario {
enum ProductKind { ... };
interface Product { ... };
interface ProducerIF {
Product getProduct();
};
eventtype ProductEvent {
public ProductKind prodKind;
};
component Producer supports ProducerIF {
provides ProducerIF
providesProduct;
publishes ProductEvent productEventSource;
};
component Consumer
uses
ProducerIF
requiresProduct;
consumes ProductEvent productEventSink;
};
}
To achieve language independency, CCM provides a programming-languageindependent so-called Component Implementation Definition Language,
CIDL. CIDL is used to describe the structure and state of component implementations. In particular, it allows defining the interfaces of components and connecting
ports of different components. Figure 5.14 gives an idea of component interface
definitions. A Producer component provides an interface (facet) for retrieving a
Figure 5.14:
cidlExample
124
product and an event source that sends out a product event whenever a new product is finished. The Consumer component has a required port (receptable) for fetching products from producers and an event sink for getting notified when a new
product is finished.
CIDL does not only allow the definition of interfaces. It also allows expressing
component compositions, persistence issues, and implementation-related aspects
Component implementation. CCM uses CORBA as an implementation platform
and provides the so-called Component Implementation Framework (CIF) for
the construction of component implementations. The CIF provides a compiler for
generating programming skeletons from CIDL descriptions. It automates many of
the basic functions of components, such as navigation between offered ports,
identity inquiries, activation, state and life cycle management.
To simplify deployment, CCM provides standardized techniques for component
packaging and deployment. It uses package descriptors in an XML-based language to describe components and their dependencies.
At runtime, component instances are managed by a two-level mechanism. Component instances are created within so-called containers, which are part of a component server. The component server creates and manages the containers. Each
of the containers manages one component implementation as defined by the CIF.
In particular, a container creates and manages its own POA. The container also
provides access to the underlying ORB and to the CORBA Object Services.
5.5
Problem 5.1:
Stubs and skeletons
Problem 5.1:
What are stubs and skeletons? What is their function in an RMI scenario? Why is
it necessary to marshal method arguments in an RMI scenario?
Problem 5.2:
How a process can
get access to a
remote reference
Problem 5.2:
Problem 5.3:
How components
written in different
languages can be
composed
Problem 5.3:
Describe two possibilities of how a process can get access to a remote reference in
RMI. What additional possibilities exist in CORBA?
Explain two different approaches of how components written in different languages can be composed. What approach uses IDLs and why?
125
Problem 5.4:
Explain the role of object adapters in scenarios where remote references (IORs)
refer to objects in non-existing server processes. Why are such scenarios supported by CORBA?
Problem 5.5:
What is the role of the Interface Repository (IFR) for CORBA's reflection capabilities?
Problem 5.6:
Why can the CORBA trading service not be used for component selection? What
additional features would be needed?
Problem 5.4:
Role of object
adapters
Problem 5.5:
Role of the
Interface
Repository
Problem 5.6:
CORBA trading
service for
component
selection
126
The business logic and database access in three-tier software architectures is handled by so-called business applications. The development of such applications is
very often supported by component frameworks that are specifically designed for
this task. The business-logic code is packaged into deployable components and
deployed into containers. The intention is that developers can focus their efforts
on the business-logic without having to worry about the supporting infrastructure.
The infrastructure is realized by containers that are part of so-called application
servers. The reasons for providing such architecture-specific component frameworks are:
the well-understood structure of the architecture,
common infrastructure and services of the application server,
the large economical importance of such architectures.
Currently, application development for three-tier software architectures is probably the most important area of component frameworks. For the future, we envisage that mature component frameworks and technologies will be used in many
other application areas. The central concepts explained here, in particular different
component kinds, architectural framework support, and dependency injection are
also relevant in other application domains. However, as currently no widely accepted component frameworks for embedded systems supporting these concepts
are available, we explain them together with application server development and
Enterprise Java Beans.
This chapter is structured as follows:
1. It introduces the basic concepts underlying application servers (Section 6.2).
2. It describes how the Enterprise Java Beans component framework supports the
development of deployable applications (Section 6.3).
3. It explains dependency injection and its role in component frameworks. In
particular, we show how dependency injection is supported by the EJB
framework (Section 6.4).
6.1
127
128
have understood what application servers are and why their development is an
appropriate area for component frameworks,
be able to develop and deploy simple Enterprise Java Beans,
understand the role of deployment descriptors,
have seen how annotations can simplify component development and deployment in the Enterprise Java Beans framework,
have learned what dependency injection means.
6.2
6.3
Enterprise JavaBeans
The Enterprise JavaBeans framework supports the development of componentbased, distributed, transactional business applications and their deployment within
application servers. Components within the framework are called Enterprise
JavaBeans or simply EJBs or beans. The EJB framework specification was created under the Java Community Process to provide public participation in the
definition and development. At the time of writing this text, the current version of
the specification was Version 3.0. Several realizations of the specification are
available. We used the open source GlassFish application server provided by Sun
Microsystems.
This section gives an overview of the EJB architecture and describes by a simple
example how beans look like, how they are deployed, how they can be used by
clients, and how applications with several beans can be built. Furthermore, we
describe the annotation technique available since EJB 3.0 that allows to simplify
the implementation of beans.
6.3.1
In the following, we will introduce the EJB component model and the overall architecture of EJB-based systems. Finally, we will briefly mention important EJBrelated aspects that are not covered otherwise in this book.
EJB component model. Enterprise JavaBeans are the components within the EJB
framework, i.e., beans are the units of composition. To be precise, one has to distinguish between the objects at runtime and the software implementing their func-
129
130
Figure 6.1:
The schematic
architecture of an
EJB-based system
There are four different kinds of beans, reflecting the different conceptual roles
and technical features of beans within a three-tier architecture:
Entity beans
Stateful session beans
Stateless session beans
Message-driven beans
5 We call it a bean component and not a bean class, because there is exactly one Java class representing the
bean, and this class is usually called the bean class.
Distinguishing between different component kinds is a typical aspect of architecture-specific component frameworks and reflects architectural knowledge. In the
following, we explain the beans characteristics.
Entity beans essentially represent the data objects handled by the system. Such
data objects describe, e.g., bank accounts, products, assurance policies, orders,
students, or employees. As suggested by the name, an entity bean instance usually
corresponds to what is called an entity in the literature about entity-relationship
modeling and database systems. Entity bean instances are persistent, i.e., their
state is stored in a database. Entity beans link the logic tier to the data tier.
The bean developer can choose between bean-managed persistence and container-managed persistence. In the first case, she has to implement the persistence mechanisms herself; in the latter case, this is done automatically by the container. An entity bean instance can be accessed by different clients communicating
with the application server. The instance is logically identified by a so-called
primary key. The primary key is used to access existing entity bean instances.
The notion of a primary key corresponds to its meaning in the database literature
(cf. [40]).
Session beans are used to model tasks, processes, or behavior of the application
server, e.g., the task of handling a certain purchase order. A session bean corresponds to a service for a client. In particular, a session bean usually accesses several entity beans. Session bean instances are conceptually not shared between clients, i.e., a session bean instance is a private resource of a client session. However, the implementation can use sharing as long as it does not affect the system's
behavior.
For efficiency reasons, the framework distinguishes between stateless and stateful
beans. Stateful session bean instances have a so-called conversational state,
which represents the continuing conversation between the bean and the client.
This state is maintained between method invocations, but it is not persistent and is
discarded at the end of the session. Stateless session beans do not maintain their
state from one method invocation to the next. They are essentially collections of
methods.
Message-driven beans support the asynchronous communication between clients
and applications. Their realization is based on the Java Message Service (JMS).
We do not explain them here, but want to stress that their existence shows the importance of asynchronous communication for certain scenarios.
Architecture. The overall architecture of a system built with the help of EJB
technology is shown in Figure 6.1. The server side consists of an EJB container
and a number of services provided by the application server. The EJB container
contains and manages the bean components and instances. We show here session
and entity beans. Beans are created via the home interface and used via the remote
interface (details regarding these interfaces will be explained in the following sub-
131
132
sections). Beans can be accessed directly by clients and can access each other in a
controlled way. Furthermore, they can access a naming service via the Java Naming and Directory Interface (JNDI), transaction monitors via the Java Transaction API (JTA), databases via the Java Database Connectivity API (JDBC), and
other services via the EJB and Java APIs. Most of the services are also accessible
by clients.
Further aspects. In accordance with the topic of this book, the presentation of
EJB focuses on its component-oriented concepts. However, we do not want to
conclude this subsection without mentioning that the EJB framework is part of the
Java 2 Enterprise Edition (J2EE) and that J2EE provides many other features for
the development of 3-tier client-server systems. In particular, J2EE offers an
elaborated technology for realizing the server-side aspects of Web-based applications (Java Server Pages, Servlets).
6.3.2
package ejb.samples.accounts;
import javax.ejb.*;
import java.rmi.*;
public interface BankAccount extends EJBObject {
String getAccountNumber()
throws RemoteException;
double getAccountBalance() throws RemoteException;
void
changeBalance(double amount)
throws RemoteException;
}
133
134
135
Bean class. The bean class provides implementations for the methods of the home
and remote interface. The only exception is the method findByPrimaryKey,
which is provided in the deployment process by the framework. The reader should
notice that this can be realized because the method findByPrimaryKey has a fixed
signature. This is one example illustrating how tightly naming and signature conventions, context rules, and interfaces play together in the EJB framework.
The bean class must not be declared as a subtype of the home and remote interfaces, i.e., it does not implement the interfaces in the sense of Java. However, it
has to provide implementations for their methods, i.e., method definitions with the
same signature as in the interfaces. The connection between the bean class and the
interfaces is established during deployment (see below).
Figure 6.2 presents a possible definition of the bean class for our example. The
attributes of the entity are fields in the entity object. The class declares getter and
setter methods for the fields and the remaining methods of the remote interface
(changeBalance in the example). It is important to use only the getter and setter
methods to access the fields of the bean. Furthermore, the class BankAccountBean illustrates that entity bean classes have to implement the interface EntityBean of the EJB framework. The methods of this interface manage the communication between the bean and the container. The method setEntityContext, for example, allows the container to store the context information of the framework in
the bean. This context information is used by the container to manage the identity
of bean instances.
Figure 6.2:
beanclass
136
6.3.3
In this section, we explain how deployment descriptors are structured and how
deployment is realized.
Deployment descriptors. The information needed to configure and deploy beans
is given by deployment descriptors. A deployment descriptor especially describes
the different parts of a bean and its runtime properties, for example whether persistence should be managed by the container or by the bean itself. It allows combining several beans into one unit, describes external dependency (e.g., to databases or external beans), and provides deployment information (e.g., access rights
specifying who has the permission to invoke a method).
EJB deployment descriptors are defined in an XML format. For the purposes of
this book, it is not important to understand the details. However, the function and
general format of such descriptions are highly relevant, as they illustrate how
component frameworks can use declarative description languages with formal
syntax to capture architectural relations and properties. Such descriptions can be
used later to deploy the software systems in a semi-automatic way.
To illustrate deployment descriptors, we consider a descriptor for the BankAccount bean. The overall structure is as follows:
<?xml version="1.0" ?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.
//DTD Enterprise JavaBeans 1.2
//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
<description>
deployment descriptor of entity bean BankAccount
</description>
<enterprise-beans>
... <!-- see extra figure and text -->
</enterprise-beans>
<assembly-descriptor>
... <!-- see text -->
</assembly-descriptor>
</ejb-jar>
The first four lines declare the XML version and the document type used. The
content enclosed in ejb-jar tags consists of an optional description, the list of enterprise beans belonging to the deployment unit (at least one bean has to be
given), and other optional parts. Above, we have illustrated the other optional
parts with an assembly-descriptor element. Assembly descriptors can define security roles used to access beans, can declare method permissions (which roles can
call which method), and can provide information about transactional behavior.
<enterprise-beans>
<entity>
<description>
BankAccount represents a bank account
</description>
<!-- name of the bean in JNDI -->
<ejb-name>BankAccount</ejb-name>
<!-- fully qualified name of home interface -->
<home>ejb.samples.accounts.BankAccountHome</home>
<!-- fully qualified name of remote interface -->
<remote>ejb.samples.accounts.BankAccount</remote>
<!-- name of bean class -->
<ejb-class>ejb.samples.accounts.BankAccountBean</ejb-class>
<!-- bean uses container-managed persistence -->
<persistence-type>Container</persistence-type>
<!-- class of primary key -->
<prim-key-class>java.lang.String</prim-key-class>
<!-- bean is not reentrant -->
<reentrant>False</reentrant>
<!-- fields of bean that should be persistent -->
<cmp-field>
<field-name>accountNumber</field-name>
</cmp-field>
<cmp-field>
<field-name>accountBalance</field-name>
</cmp-field>
<!-- field in bean corresponding to primary key -->
<primkey-field>accountNumber</primkey-field>
</entity>
</enterprise-beans>
Figure 6.3 shows the entity part of the deployment descriptor for the BankAccount bean. The comments (enclosed in <!-- ... -->) describe the different elements
of the descriptor. The deployment descriptor for an entity bean declares in particular which fields of the bean should be made persistent.
137
Figure 6.3:
entityPartDeploym
ent
138
Figure 6.4:
Generation of EJB
home and EJB
remote class
Figure 6.5:
Bean access via
EJB home and EJB
remote object
The EJB home and EJB remote objects encapsulate the bean instance. A client can
only access a bean by invoking methods of the home and remote objects. These
objects delegate the client's method invocations to the bean instance (see Figure
6.5). In addition, the EJB home and EJB remote objects perform operations realizing important tasks for the container, in particular resource management, security
mechanisms, transactions handling, and persistence management. How these operations are implemented depends on the server developer. It is important that the
EJB framework supports such a delegation and encapsulation mechanism:
In this way, the container has control over bean instances and can prevent incorrect or malicious access to beans.
It is the basis for relating the component infrastructure to the components.
It largely frees the bean developers from infrastructure issues.
The use of EJB home and remote classes also explains why the bean class must
not be a subtype of the home and remote interfaces, i.e., why the bean class does
not implement the interfaces in the sense of Java. The implementation of these
interfaces is provided by the EJB home and EJB remote classes. However, in order to enable delegation, the method signatures of the home and remote interfaces
have to match those of the bean class.
6.3.4
The initial context is needed for the naming service. After lookup, the client has to
narrow the resulting reference to assign it to a variable of the type BankAccountHome. Performing the narrowing with a method and not with a simple cast enables the EJB framework to perform additional operations at that point. The
BankAccountHome object is then used to create a new empty bank account with
139
140
the account number "23456735". This leads to the creation of a bank account instance, an entity in the underlying database, and a primary key object that will be
associated with the bean instance. Now, the client can make use of the bean's
functionality. To illustrate this, the last line shows an invocation of the method
changeBalance.
In summary, we can see that the use of beans is similar to the use of remote objects. The management of beans by the container is hidden from the client.
6.3.5
In this section, we demonstrate session beans and server-local beans and show
how they work together. Local beans can be accessed directly by other local beans
without the overhead caused by remote method invocation. In the next section, we
will repeat this example in a version using the Java annotations available since
EJB 3.0 to illustrate the simplifications that are possible with this annotation
mechanism.
As a running example, we use a banking bean with the following interface:
public interface Banking extends EJBObject {
void transferMoney(String fromAccNo,
String toAccNo, double amount)
throws RemoteException;
double getAccountBalance(String accNo)
throws RemoteException;
}
The Banking bean is a session bean that allows transferring money from one account to another account and looking up up the balance of an account. As a session bean, the home interface only needs a create method:
public interface BankingHome extends EJBHome {
public Banking create()
throws CreateException, RemoteException;
}
141
fromAcc.changeBalance( -amount );
toAcc.changeBalance( amount );
}
public double getAccountBalance(String accNo) {
return findByAccountNumber(accNo).getAccountBalance();
}
private BankAccount findByAccountNumber(String accNo) {
try {
InitialContext ictx = new InitialContext();
Object o =
ictx.lookup("java:comp/env/LocalBankAccount");
BankAccountHome bah = (BankAccountHome) o;
return bah.findByPrimaryKey(accNo);
} catch (Exception e) {
... // Exception handling
return null;
}
}
// the methods of interface javax.ejb.SessionBean
// are omitted here; they are similar to Sect. 6.3.2
}
The banking bean class is shown in Figure 6.6. As a session bean, it implements
the interface SessionBean. The developer-defined method findByAccountNumber
shows how other beans can be accessed. Similar to the remote case, the connection to bank account beans is established via the JNDI lookup mechanism and the
primary key. As it is a local lookup, the JNDI prefix is java:comp/dev. The
name of the local bean class is defined in the deployment descriptor, which in this
case is LocalBankAccount (cf. Figure 6.7). Furthermore, it should be noted that
the returned bank account home object is only cast to the corresponding type. A
narrow operation is not necessary here, because the returned reference is a reference to a local object.
The local implementation of the bank account bean (not shown) is almost the
same as the remote one discussed in the subsections above. The only difference is
that the remote interface is now a local interface extending EJBLocalObject
and the home interface extends EJBLocalHome. Furthermore, the methods do
not throw remote exceptions.
<enterprise-beans>
<session>
<ejb-name>Banking</ejb-name>
<home>ejb.samples.accounts.BankingHome</home>
<remote>ejb.samples.accounts.Banking</remote>
Figure 6.6:
BankingBean
142
Figure 6.7:
deploymentDescrip
torBankingBean
<ejb-class>
ejb.samples.accounts.BankingBean
</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<ejb-local-ref>
<ejb-ref-name>LocalBankAccount</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>
ejb.samples.accounts.BankAccountHome
</local-home>
<local>ejb.samples.accounts.BankAccount</local>
<ejb-link>BankAccount</ejb-link>
</ejb-local-ref>
</session>
<entity>
<description>
BankAccount represents a bank account
</description>
<ejb-name>BankAccount</ejb-name>
<!-- fully qualified name of home interface -->
<local-home>
ejb.samples.accounts.BankAccountHome
</local-home>
<!-- fully qualified name of local interface -->
<local>ejb.samples.accounts.BankAccount</local>
<ejb-class>
ejb.samples.accounts.BankAccountBean
</ejb-class>
<!-- other elements are as descriptor in 6.3.3 -->
</entity>
</enterprise-beans>
}
The main parts of the deployment descriptor for the example are shown in Figure
6.7. The deployment descriptor contains information about both beans and shows
how session beans and local beans are treated in the XML format. The most interesting part is the ejb-local-ref element of the session bean description. It declares
the name under which the local bean is accessible and the names of its classes.
Finally, we illustrate the use of banking beans by clients. The steps are the same
as those explained in Subsection 6.3.4:
InitialContext ctx = new InitialContext();
Object o = ctx.lookup("java:global/bankunit/Banking");
BankingHome bah = (BankingHome)
PortableRemoteObject.narrow(o,BankingHome.class);
Banking ba = bah.create();
ba.transferMoney("12345", "897979", 500.0);
6.3.6
Even with the simple banking example from the last section, it becomes quite
clear that the explicit declaration of remote and home interfaces as well as the
formulation of the deployment descriptor can become a burden for programming
and program maintenance. That is why EJB 3 provides a mechanism for simplifying bean development by introducing annotations. With annotations,
home interfaces are no longer needed for remotely accessible beans
local and home interfaces are no longer needed for local beans,
beans access is simplified,
deployment descriptors can be simplified or avoided,
dependency injection is supported (see Section 6.4).
In this section, we demonstrate the mechanism by rewriting the example of Subsection 6.3.5 with annotations.
The banking bean gets the following interface with the annotation @Remote:
@Remote
public interface Banking {
void transferMoney(String fromAccNo,
String toAccNo, double amount);
double getAccountBalance(String accNo);
}
It should be noted that this version of the interface Banking does not extend
EJBObject. Furthermore, the declaration of remote exceptions is no longer necessary. A home interface for Banking need not be defined.
@Stateless
public class BankingBean implements Banking {
@PersistenceContext(unitName="bankcontext")
private EntityManager em;
public void transferMoney(String fromAccNo,
String toAccNo, double amount){
BankAccountBean fromAcc= findByAccountNumber(fromAccNo);
BankAccountBean toAcc = findByAccountNumber(toAccNo);
143
144
fromAcc.changeBalance( -amount );
toAcc.changeBalance(amount);
}
public double getAccountBalance(String accNo) {
return findByAccountNumber(accNo).getAccountBalance();
}
private BankAccountBean findByAccountNumber(String accNo){
return (BankAccountBean)
em.find(BankAccountBean.class, accNo);
}
Figure 6.8:
BankingBeanV3
As shown in Figure 6.8, the bean class for Banking is declared to be a stateless
session bean implementing the interface Banking. That is, it no longer implements
the interface SessionBean. Compared to the version in Figure 6.6, it is simplified
in two ways:
The methods of the interface SessionBean need not be implemented. The
connection to the entity context is handled by dependency injection (see Section 6.4) based on the annotaion:
@PersistenceContext(unitName="bankcontext")
The method find of the entity manager provides more direct access to the bank
accounts.
Whereas a deployment descriptor is no longer necessary, a small XML document
is needed to describe the persistence context. This document refers to the name
"bankcontext" in the persistent context annotation and describes the connection to
the underlying database or data layer.
@Entity
public class BankAccountBean implements Serializable {
private String accountNumber;
private double accountBalance;
public BankAccountBean() {}
public BankAccountBean(String accNo, double initBal) {
setAccountNumber(accNo);
setAccountBalance(initBal);
}
@Id
public String getAccountNumber() {
return accountNumber;
}
private void setAccountNumber(String accNo) {
accountNumber = accNo;
}
public double getAccountBalance() {
return accountBalance;
}
private void setAccountBalance(double accBal) {
accountBalance = accBal;
}
public void changeBalance(double amount) {
setAccountBalance( getAccountBalance()+amount );
}
// no methods missing !!
}
Using the annotations, the realization of the entity bean BankAccount only consists of the one class shown in Figure 6.9. In this version, the bean class is not a
subtype of the interface EntityBean, so that the implementation of the inherited
method signatures is avoided. The annotation @Id marks the getter method returning the primary key.
Finally, the use of beans is simplified because the new version avoids the detour
via the home object:
InitialContext ctx = new InitialContext();
Object o = ctx.lookup(Banking.class.getName());
Banking ba = (Banking) o;
ba.transferMoney("12345", "897979", 500.0);
6.3.7
145
Figure 6.9:
BankAccountBean
V3
146
Some of the rules and conventions have already been explained along with the
above description. To get a broader view, we provide a few more rules. The
reader is encouraged to figure out why such restrictions are imposed on the components:
A bean must not return the implicit parameter this as the result of a method
or pass it as a parameter.
Static variables are forbidden in bean classes.
Use of threads and synchronization mechanisms is not allowed.
Use of GUI facilities as well as input- and output-operations are forbidden
(this includes socket communication).
Use of introspection and reflection must be avoided.
Beans should not influence class loading and security management.
The basic reason underlying these restrictions is to create a well-defined interface
between the components and the container, or in more general terms, between the
components and their infrastructure. This interface has to take into account behavioral and security aspects. For example, deadlocks have to be avoided, and the access rights of beans have to be restricted so that they cannot manipulate the data
of other beans.
Managing beans. In addition to controlling the life cycle of beans, the container
is responsible for the efficient management of bean instances. As a container has
to deal with many clients simultaneously, it cannot keep all instances in its main
memory. That is why many containers provide mechanisms for passivating and
reactivating instances by temporarily storing them on disk. To reduce the number
of needed instances, containers share instances or keep instances of the same type
in pools. Pooling of instances allows reuse of an instance, thereby avoiding the
situation that an instance is removed and afterwards, an equal instance is created
again. Another technique for increasing performance is support for load balancing between different containers: If one container is very busy, use another less
frequented container. Of course, load balancing has to be realized in such a way
that it is transparent to clients.
Persistence. According to the Enterprise JavaBeans specification, EJB containers
have to support a mechanism that is called container-managed persistence. An entity bean with container-managed persistence relies on the container to perform
persistent data access on behalf of the bean instances. The container has to automatically transfer the data between the bean instance and the underlying database.
In particular, it implements the creation, removal, and lookup of the entity object
in the database. Furthermore, it manages the mapping between primary key and
EJBObject.
6.4
147
148
6.4.1
To explain inversion of control and dependency injection, we consider a component C and an initially required service S of C. We distinguish four patterns of
how C can be connected to S:
1. The component code of C creates an instance of a component D that implements S. Then, C connects itself to S.
2. The component code of C looks up S in the context and connects to it.
3. The component C comes together with an activator code that creates and initializes instances of C. The activator looks up the service in the context and
passes it to the component instance (the TCF headers, the OSGi activator
classes, and the EJB home classes are examples of such activator code).
4. The component container, i.e., the component-independent code, provides the
instances of C with the service S.
In the first pattern, C has full control over which component it creates to get service S. In the other patterns, it is the context and the container that control which
implementation of S is used. This form of inversion of control6 allows component frameworks to automate parts of the instantiation and connection process of
components under certain conditions.
The second and third pattern above use so-called service locators in the context
of a component to look up a service. In component frameworks, service locators
are usually provided by the framework. In OSGi, for example, the method getServiceReference of the class BundleContext acts as service locator (see Chapter 4).
Using service locators has the advantage that component code is decoupled from
the code that provides the service. However, it is still the component code or the
activator that has to perform the lookup and set the service reference. If an application consists of many components and the components have many dependencies, a lot of code has to be written for making all the connections. Automated dependency injection aims to avoid such coding and to establish the connections
automatically based on declarative dependency definitions.
Dependency injection means establishing the connection of a component C to its
(initially) required services from the outside of C. In a component framework, it is
usually the container that injects the service references. Three forms of injection
are used:
6 There are other forms of inversion of control; for example in the area of graphical user interfaces, inversion of control means that the user interface takes over control from the application
code.
6.4.2
149
150
The PhoneNumber bean provides a method that takes a name and a zip code and
and returns the corresponding telephone number; as stated above, we do not use
an extra interface for this bean to demonstrate direct injection of beans (see below):
@Stateless
public class PhoneNumberBean {
public String getNumber(String name, String zipcode) {
... ; return ...;
}
}
The PhoneNumberNA bean allows remote access and provides a method that
takes a name and an address and returns the corresponding telephone number. The
remote interface is simply declared as:
@Remote
public interface PhoneNumberNA {
public String getNumber(String name, String addr);
}
The most interesting part is the bean class for this interface:
@Stateless
public class PhoneNumberNABean implements PhoneNumberNA {
@EJB
Address2ZipCode a2z;
@EJB
PhoneNumberBean nz2p;
public String getNumber(String name, String addr) {
return nz2p.getNumber(name, a2z.getZipcode(addr) );
}
}
151
This bean has two dependencies. It depends on the service Address2Zipcode and
on the PhoneNumber bean. The dependencies are expressed by the annotation
@EJB. The EJB framework derives from this annotation that it has to inject
a service implementing Address2ZipCode into the PhoneNumberNA bean
a PhoneNumberBean into the PhoneNumberNA bean.
The first dependency is called an interface dependency, the second a class dependency. The framework especially provides the setter methods for the injection. Using the dependencies, the PhoneNumberNA bean implements the method
getNumber.
The important message is that the code above is complete. It can be deployed into
an EJB container and the container creates the needed beans and establishes the
connections automatically by dependency injection when the PhoneNumberNA
bean is used. Here is a fragment illustrating the use of the bean:
InitialContext ctx = new InitialContext();
Object o = ctx.lookup(PhoneNumberNA.class.getName());
PhoneNumberNA na2p = (PhoneNumberNA) o;
System.out.println(na2p.getNumber(
"Guido Merkel","Paul-Beliebig-Strae 42;Bonn"));
6.5
Problem 6.1:
Problem 6.1:
Frameworks
Problem 6.2:
Explain the program parts of a bean and their role in the EJB framework.
Problem 6.2:
Program parts of a
bean
152
Problem 6.3:
Delegation
mechanism
Problem 6.3:
Why does EJB use a delegation mechanism between the EJB home and remote
objects and the instances of the bean class?
Problem 6.4:
Simplifications
explained in 6.3.6
Problem 6.4:
Problem 6.5:
Restrictions on
beans
Problem 6.5:
Problem 6.6:
Dependency
injection
Problem 6.6:
How could the simplifications explained in 6.3.6 be achieved? What does this
mean more generally for architecture-specific component frameworks?
Explain why the restrictions named in the paragraph Conventions and rules are
imposed on beans.
What are the advantages and disadvantages of the three described forms of dependency injection?
7.1
7.2
The component frameworks presented in the last chapters make platform assumptions that can often not be met in embedded systems. For example, OSGi assumes
a Java Virtual Machine, most CORBA implementations use TCP/IP as the underlying communication layer, and EJB is based on application servers with large
memory requirements and a number of additional platform assumptions. These
resources are not or only partially available in smart cards, field devices, consumer electronics, mobile phones, and vehicles.
Embedded software and thus component technology for embedded software is
faced with the following special requirements:
153
154
Diversity of the underlying platforms: Embedded systems run on very different, often dedicated kinds of hardware and use a variety of software for operating system and communication functionality.
Stricter resource limitations: For reasons of cost, size, and energy consumption, memory and computation power is kept as low as possible and has to be
used with much more care than in business applications on general-purpose
platforms.
Non-functional properties: As embedded software is often used for the control
of real-world devices (valves, car speed, aircrafts, etc.), real-time constraints
and quality-of-service properties play a crucial role.
To meet these requirements, component frameworks for embedded systems have
to support components with different execution models (synchronous, asynchronous, clock-synchronous, real-time), different communication models (synchronous vs. asynchronous) and different scheduling paradigms. Frameworks must
allow the construction of systems that use several of these models. To handle nonfunctional properties, component models must be equipped with information
about runtime and quality of service properties (see, e.g., rich components [21] or
the Palladio component model [10]).
Another aspect of component-based approaches for embedded systems is the larger development gap between the component descriptions and their deployed implementations. The gap is larger than in business application areas, because it is
more difficult to
find a component mapping to the more complex and heterogeneous platforms,
satisfy the non-functional properties, and
select appropriate scheduling and timing parameters.
To overcome these additional problems, there is a trend towards hybrid approaches combining component-based and model-based development by integrating refinement steps into the component frameworks (we will briefly discuss this
issue together with AUTOSAR below).
7.3
This section sketches some approaches of CBSD for embedded systems. We selected the approaches to illustrate certain aspects of our topic. We do not claim
that the selection gives a balanced overview of the field (see, for example, [2] for
a presentation and comparison of several other interesting component approaches).
7.3.1
Pin [23]) is a basic, simple component technology for embedded software developed at the Software Engineering Institute (Pittsburgh, USA). Pin has been used
in different application domains, e.g., in substation automation and robotics. Its
design was governed by the following major objectives:
1. Simple programming model with an execution model following the semantics
of UML statecharts
2. Adaptable to the needs of new applications and platforms
3. Provision of means to enforce design and implementation constraints that are
extrinsic or later added to Pin
4. Support of the basic features for building predictable embedded software.
Pin can be considered as an extensible and adaptable prototype component
framework. It has been used to develop predication-enabled component technologies (PECTs) in different application domains.
Pin supports a non-hierarchical component model. Components can be specified
by UML statecharts. All needed APIs are available in the programming language
C. A component can receive messages at a finite number of so-called sink pins
and send messages over a finite number of so-called source pins. The behavior of
a component is defined in terms of a finite number of so-called reactions. A reaction can be considered as a thread waiting for incoming messages and producing
outgoing messages. Each reaction is responsible for handling the messages at a
subset of the sink pins.
The implementation of each component is put into a prefabricated generic container, that is, there is one container per component. Containers manage the life
cycle of components and the communication with other components, realize the
connections to the underlying runtime environment with real-time extensions, and
provide interfaces for component analysis. The connections between components,
that is, between source and sink pins, are configured statically and cannot be
modified at runtime. A construction and composition language CCL is available
to describe such assemblies. Pin supports synchronous and asynchronous message-based communication between components, but does not support distribution.
The component model defines rules for how components may interact with one
another and how they share resources. The runtime environment enforces these
rules of interaction and provides basic services for resource sharing, communication, scheduling, and further configuration aspects.
155
156
In summary, Pin is a component framework for concurrent, non-distributed embedded systems providing a lightweight, message-based, flat component model
with efficient implementations in C and basic services for resource management.
7.3.2
PECOS ([34]) was a project for the development of a component-based technology for so-called field devices. Field devices are small embedded hard real-time
systems for automatically controlling processes in the real world. They collect
data via sensors, e.g., for temperature, pressure or speed; process the data, and
control actuators like valves or motors to affect the real world (cf. the ThermoControl example in Chapter 1). A typical field device has a small micro-processor
with 1 MB of ROM and even less RAM. It usually communicates over dedicated
bus systems with other devices.
PECOS is an example of a domain-specific approach, that is, it aims to address
the needs in the sketched domain. It is interesting because of its specific component model. As in models underlying synchronous languages (see [12]), PECOS
components communicate over shared variables called ports. These ports can be
written by one component and read by other components, meaning that components communicate by means of information flow. Control flow is treated separately (see below).
The PECOS model is hierarchical and distinguishes between so-called leaf components, which have no internal structure, and composite components, which are
statically composed of other components. For the client of a component, the internal component structure is not visible; that is, the client cannot distinguish between leaf and composite components. Concerning the dynamic behavior, the
PECOS model supports three different component kinds:
Passive components: They do not have their own thread of control. They are
triggered from active enclosing components and are used to realize a function
that executes synchronously and completes within a "short time".
Active components: They have their own thread of control and realize longerliving activities. In particular, the complete software of a field device is usually modeled as a composite active component.
Event components: Their behavior is triggered by external events. They are
used, in particular, to model hardware components and integrate them into the
component model.
In the PECOS model, synchronization and scheduling of components is explicitly
described (that is, there is no global clock as in the clock-synchronous component
model described in Subsection 2.4.5). Every composite component has a scheduler. Petri nets are used to model the scheduling and analyze the validity of compositions.
7.3.3
Different component models have been developed for the domain of consumer
electronics (see [32] for an analysis of the specific requirements). One specific
aspect in this domain of embedded software is the high diversity of products and
the resulting need to support variants within the component model. In the following, we introduce the Koala component model ([35]) to illustrate these aspects.
Koala incorporates concepts from architecture description languages (in particular
from Darwin) and product-line engineering (cf. Subsection 3.4.2).
The software in consumer electronic (CE) products realizes a large range of functionality from basic hardware control via signal and data processing, support for
product features like electronic programming guides and graphical user interfaces,
all the way to integration with Web technology. CE products have become a
member of complex product-family structures and CBSD for such products has to
cope with this aspect.
Koala strictly separates component and configuration development. Components
have provided and required interfaces where an interface consists of a small set of
related C function signatures (the implementation language of Koala as presented
in [35] is C). Even general services and system calls like memory management
have to be handled through required interfaces. Koala supports a component description language CDL to specify the interfaces of a named component and to
construct compound components; that is, it supports a hierarchical component
model. Components can be linked directly by connecting required to provided interfaces using CDL. In addition, so-called modules can be used to glue interfaces
together. The glue code may depend on configuration parameters.
To handle diversity without losing resources at runtime, Koala combines flexible
parameterization mechanisms for components with partial evaluation techniques.
Koala components are explicitly parameterized across all their configurations. Instead of handling the parameters as component properties that have to be set from
the outside, Koala uses so-called diversity interfaces. A diversity interface is a
required interface through which a component asks the environment for the properties. Components use the diversity interfaces at configuration time to set internal
parameters. In addition to that, so-called switches are provided to handle structural diversity in compound components. A switch allows linking an interface of
one component to two or more alternative component interfaces depending on
configuration parameters.
At configuration time, the actual configuration parameters are provided from outside the component. A partial evaluation process evaluates all expressions that
157
158
7.3.4
AUTOSAR (see www.autosar.org) is a joint initiative of a number of major players in the automotive industry to develop an AUtomotive Open System ARchitecture and to overcome the current situation, which is characterized by proprietary
solutions that hinder the exchange of components and applications among automotive OEMs and suppliers. As the name already suggests, the initiative goes beyond the development of a component framework. We consider the approach here
to illustrate the close relationship between architecture, component frameworks,
and development method in an important embedded system domain.
Figure 7.1:
Overview of the
two-layered
AUTOSAR
approach
159
7.4
Problem 7.1:
For each of the frameworks presented in this chapter, name at least one feature,
aspect, or characteristic that goes beyond general component frameworks.
Problem 7.1:
Restrictions on
beans
160
This chapter briefly reviews the central conceptual aspects treated in the book and
and sketches current lines of research and development in CBSD.
8.1
Review
The goal of the book was to introduce the motivation for CBSD and to explain the
main concepts and important frameworks. Keeping away from the details and using many simplifications, our aim here is to review the story of the book.
Chapter 1 - Introduction. When complex systems are built, it is a good idea to
have a clear view of what the components are: Divide et impera! Having a clear
notion of software components that allows analyzing the components one by one
simplifies techniques for quality assurance, in particular testing and verification.
Furthermore, complete systems composed from well-understood components are
easier to maintain and to evolve because one can exchange single components or
groups of components, knowing which effects this has on the overall system behavior.
Components also help to organize and improve the development process. Different components can be developed separately by different groups. Specialists can
take care of the integration and component frameworks and the infrastructure provided by it. Other engineers can handle configuration and deployment. And components are units of reuse, reducing the development effort, shortening time to
market, and enabling competition on a market of software components.
With this huge motivation, it should be easy to settle on a well-defined component
notion. However, it is not. As illustrated in Section 1.3, components can be identified on all abstraction levels of system descriptions and can be parts of different
views in such a way that there is no natural refinement relation between them.
Following the literature, we have focused on a notion of software components that
links design, implementation, deployment, and system administration. Strictly
speaking, this means that there is a well-understood relationship between a design
component, the program code implementing the component, the deployment units,
and the runtime components. Based on this notion, CBSD can be developed as a
software engineering discipline that exploits the potential of this paradigm with
respect to specification of components, implementation support for components,
quality assurance methods, development processes, tooling, as well as component
reuse and qualification.
Chapter 2 Component software. As software components are parts of software
systems, we started the investigation by looking at complete software systems,
their manifestations, and their different development stages. The same development stages also apply to software components. We discussed component specifi-
161
162
The distinction between components, containers, framework APIs, and platforms, and how containers provide the context for components and their communication.
Aspects and states of the deployment of bundles and the technical details that
are necessary to handle deployment units in an automated way.
The relationship between program code/packages and service-providing components: A bundle can contain provide packages and components.
The reflexivity and dynamic aspects of components, in particular how a component can listen to events concerning other components.
Along with the explanations, we provided an introduction to OSGi.
Chapter 5 Distribution and heterogeneity. Component frameworks develop
their full strength when they allow the integratation of components written in different languages and deployed on distributed platforms with different operating
systems. We introduced the central concepts of remote method invocation for realizing communication among distributed objects and components. Furthermore,
we explained direct and brokered composition of heterogeneous components. The
main part of the chapter described CORBA's solution to the challenge of distribution and heterogeneity.
Chapter 6 Architecture specialization. We investigated the Enterprise JavaBeans framework as a prominent representative of an architecture-specific component framework. From a conceptual point of view, it is important to understand
the gain that can be achieved by architecture- or domain-specific component frameworks. In particular, different kinds of components can be supported that capture the requirements of specific roles in the architecture or domain. We demonstrated how declarative XML-based descriptors can help in deployment and how
code generation techniques can separate client and component code. Finally, we
explained the important concept of dependency injection and showed how EJB
supports automated dependency injection based on Java annotations.
Chapter 7 Embedded systems. Frameworks for the development of embedded
systems are usually domain-specific. The development of widely used frameworks is hindered by the special technology and real-time constraints in different
domains.
8.2
Current developments
163
164
ded systems. Other areas of development where CBSD makes important contributions are service-oriented architectures (see Subsection 3.4.2 for the relationship
between SOA and CBSD) and product-line engineering. In the remaining paragraphs, we will sketch some current lines of research in the area of CBSD to illustrate open questions and possible future trends.
Software evolution. CBSD integrates aspects of design, implementation, deployment, and runtime management. All these areas are currently supported by
different technologies and tools, e.g.,
UML for modeling and design,
programming language technology for implementation,
version and configuration management systems, package managers, and build
tools for deployment, and
specific runtime managers for administering systems at runtime.
Research on component models and component technology aims to bridge the gap
between these areas in order to simplify software maintenance and evolution. Another important area is that of automated adaptation techniques.
Language support. Many researchers are working on better language support for
CBSD. This includes more powerful module systems and component frameworks
(see, e.g., [15]), better language support for component models in programs,
higher-level language mechanisms for composition and coordination of services
and components, as well as constructs for integrating architectural descriptions
and programs.
Specification and formal methods. Software components should have welldefined interfaces and a behavior that can be described independent of the contexts in which they are used. Research on specification and verification techniques
aims to provide language and verification support for formally specifying component properties and verifying that a component implementation satisfies its specification. Successful steps in this direction have been taken (see, e.g., the SOFA
model in [2]), but they are not yet powerful enough to be applied to practical
component frameworks like OSGi or EJB. Furthermore, there are still a lot of
open questions and challenges in the area of specification and verification of nonfunctional component properties such as quality of service or real-time properties.
Retrieval techniques. Retrieval of reusable components has long been a research
area in software engineering. Retrieval depends on how the components are described. It can be based on meta-data for the classification of components. Here,
the research challenge is to develop appropriate semantic schema technology for
component retrieval. Another line of research aims to retrieve components based
on functional or behavioral search criteria, for example by employing specification matching techniques.
165
Solution to
problem 1.1
166
Solution to
problem 1.2
Solution to
problem 1.3
Solution to
problem 1.4
Solution to
problem 2.2
167
Solution to
problem 2.3
Solution to
problem 2.4
Separate development needs clear interfaces between the component and program
parts that should be developed separately and a precise description of the component's behavior. This is what a specification provides. The specification can be
considered as a contract between clients of the component and the providers or
developers of the component.
Solution to
problem 2.5
Threads are not component-local entities; they can be created in one component
and execute methods/code of other components (also see Section 2.4.2). Without
appropriate synchronization, several threads can execute concurrently within a
component instance and cause data races and state inconsistencies. On the other
hand, without knowing a component's context, conservative synchronization may
lead to unexpected deadlocks.
The declaration of the attributes as volatile is necessary because the corresponding
instance variables are written and read by different threads. Without the volatile
declaration, the synchronization of Java does not guarantee that the thread created
by the Thermostat component observes the modifications to the variable desiredTemp done by the thread of the ControlPanel.
Solution to
problem 2.6
168
Solution to
problem 2.7
Solution to
problem 3.2
Solution to
problem 3.3
Solution to
problem 3.4
Solution to
problem 3.5
169
Solution to
problem 4.1
Two components are equal if they have the same name, specification, and body.
Thus, it is not sufficient that they have or satisfy the same specification (cf. Subsection 4.2.4).
Solution to
problem 4.2
Solution to
problem 4.3
Solution to
problem 4.4
Bundles can provide packages (static aspect) and services (dynamic aspect), see
the beginning of Subsection 4.3.2 for an explanation and the rest of the subsection
for examples. The dynamic aspect reflects the provided interfaces and services of
components, that is, it reflects what clients want to have. The static aspect is necessary to share and effectively structure the code that components need to provide
the services.
Solution to
problem 4.5
170
Solution to
problem 5.2
Solution to
problem 5.3
Solution to
problem 5.4
171
Solution to
problem 5.5
The IFR provides the meta-data about IDL types and makes it available to programs using CORBA (see the paragraph "Reflection capabilities" of Subsection
5.4.3).
Solution to
problem 5.6
The CORBA trading service (see last paragraph of Subsection 5.4.3) is designed
to select a service. A component can provide multiple services and has additional
crucial properties, such as its implementation language and its platform restrictions. To use trading services for component selection, these and additional properties have to be covered by the Trader Constraint Language as well. In addition, a
download, adapt, and deploy mechanism is desirable, which is not needed for service trading.
Solution to
problem 6.1
An architecture-specific framework can tailor the services and the supported component kinds to the needs of the architecture and application domain. All generic
aspects of the architecture can be covered by the framework. The EJB framework,
for example, provides generic support for persistence, transactions, and security,
and the PECOS framework is tailored towards the needs of field devices (Subsection 7.3.2). This kind of role separation between framework developers and application developers has a huge potential for saving development efforts and costs.
Solution to
problem 6.2
The program parts of the beans are listed at the beginning of Subsection 6.3.2.
Their roles are explained in the rest of Subsection 6.3.2 and the succeeding subsections.
Solution to
problem 6.3
172
and to keep the code for the business logic realized in beans separate from the
infrastructure code.
Solution to
problem 6.4
Solution to
problem 6.5
Solution to
problem 6.6
173
Solution to
problem 7.1
174
175
Index
Index
A
ACC
C
37, 40
callback
actions
37
callback scenario
activator class
84
CBSD
1, 54, 55, 61
active
26
CCM
122
active component
v, 156
29
changing requirements
57
activity
37
CIDL
actor
18
code categories
36
code maintenance
57
code view
64
adaptation
v, 59
administration
aggregation
75
analysis
123
23, 129
AOSD
66
component activity
26
Apache Felix
83
component adaptation
58
application deployer
component body
77
application provider
component composition
31
application server
127
component container
application-independent
129
component description
31
63
component framework
vi, 49, 71
architectural views
architecture
157
architecture-specific
127
component header
77
123
66
component infrastructure
48
asynchronous
40
component instance
31
AUTOSAR
component kind
component model
131, 156
vi, 15, 37, 48, 129, 155, 157
component provider
bean component
130
component qualification
59
bean composition
147
component retrieval
58
bean instance
130
component selection
58
bean-managed persistence
131
component server
behavior
v, 162
component software
19
74
black-box
59
component specification
black-box reuse
72
brokered composition
bundle event
bundling
108
96
75, 82
124
v, 1,
53, 55
component-based software system
composition
v, 54
19
business application
127
composition kind
vi, 162
business logic
128
composition mechanism
75
conceptual view
64
configuration
18
176
Index
configuration mechanism
81
entity bean
connecting
76
environment
connector
38, 42
consumer electronics
container
153, 157
container-managed persistence
130
vii, 18
event component
156
evolution
57
evolvability
131
execution model
154
context
77
execution view
64
contract
10
convention
145
CORBA
122
facet
field device
coupling
60
flexibility
cross-cutting concern
66
framework event
CSC
37, 46
153, 156
3
96
framework provider,
data tier
128
dependency injection
deployment
122
deployment descriptor
vi
deployment mechanism
generation technique
glue code
59
design
heterogeneity
design decision
heterogeneous
gray-box
81
vii, 32, 46
goal
138
deployment rule
138
108, 163
108
heterogeneous composition
20
development process
11
hierarchical
development stages
vii
hierarchical component
99
hierarchical composition
20
direct composition
108
distribution
diversity
154
diversity interface
157
53
dynamic
64
dynamic aspect
91
dynamically required
147
IDL
109
IFR
120
implementation
implementation layer
implementation repositories
information hiding
Eclipse Equinox
83
49, 101, 129, 153, 164
embedded system
encapsulation
Enterprise JavaBeans
Enterprise JavaBeans (EJB)
IDL mapping
IMR
E
EJB
156, 157
infrastructure
initially required
vii, 8, 10, 29
72
111
111, 119
53
10, 96
147
vii, 153
integration
61
53
interceptor
119
129
interface
vii
interface automaton
44
vii
177
Index
109
vii
109
105
100
OEM
vii
Interface Repository
120
OMG
109
105
open composition
inversion of control
101
ORB
IOR
105, 111
20, 32
package management
L
language mapping
111
outsourcing
K
kinds of composition
OSGi
20
91
Palladio
154
partial evaluation
157
passive
26
layered
17
logic tier
128
passive component
viii
Passive component
156
PECOS
156
logical component
logical view
64
persistence
maintenance
manifest file
84
viii, 146
Pin
155
platform
platform assumption
153
105
platform evolution
57
MDA
66
port
message queue
40
predictable
message-driven bean
130
prerequisite
method binding
107
presentation tier
mobile phone
153
product-line engineering
model variable
23
program component
model-driven development
67
provided port
64
motivation
2, 161
MTC
37, 39
multi-language
108, 112
multi-language support
100
multi-threading
39
name
74
name space
74
154, 164
O
object adapter
12
128
111
67, 157
viii
22
Q
qualification
11
quality
quality assurance
11, 60
quality control
60
quality of service
non-functional
120, 154
R
receptable
122
reflection
120
reflexivity
99
104
viii
178
Index
103
stage
19, 162
remote reference
106
state
23
23
rendezvous
43
state space
required port
22
130
130
requirement
5, 153, 157
resource
153
static
64
resource limitation
154
static aspect
91
retrieval
164
reuse
3, 53
rich component
154
RMI
104
role separation
3, 58
stub
105
synchronization
39
system administrator
T
TCF
76
RPC
103
ThermoControl example
20
rule
145
thread
39
S
SCC
scheduling
security
sensor
separate development
37, 43
154, 156
time to market
trading service
121
transaction
147
transition
98, 147
18
3
44
UML statechart
155
unmarshaling
105
servant
105
service
76
service event
96
service-oriented development
66
variant
157
shared variable
156
vehicle
153
skeleton
105
verification
164
smart card
153
version number
74
versioning
98
view
63
SOA
65
software architecture
60, 61
software component
viii, 2, 6, 15, 74
software evolution
57, 164
software modeling
10
software specification
10
software system
specification
16, 18
22, 164
9
64
W
white-box
59
Index
179