Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Managing Class Names in Java Component Systems with Dynamic Update Petr Hnětynka, Petr Tůma Charles University Department of Software Engineering Distributed System Research Group Malostranské náměstí 25, 118 00 Prague 1, Czech Republic [email protected] [email protected] Abstract: This paper deals with class and interface name clashes in Java component systems that occur because of evolutionary changes during the lifecycle of a component application. We show that the standard facilities of the Java type system do not provide a satisfactory way to deal with the name clashes, and present a solution based on administering the names of classes and interfaces with a version identifier using a byte code manipulation tool. The solution is demonstrated on a proof of concept implementation. Keywords: Components, Java, versioning, classes, interfaces, name clashes. 1 Introduction The introduction of components into the software engineering process brings the benefits of easier reuse and dynamic integration. Being elements of reuse and integration, the components follow the typical lifecycle of applications [9] and are therefore subject to evolutionary changes that may change both their internals and their interfaces. Because of features such as platform independence, type safety, garbage collection, dynamic loading and others, both the component platforms and the components themselves are often developed in Java [8]. The components are typically sets of Java classes and interfaces, evolutionary changes to the internals and interfaces of components thus require changes of the corresponding Java classes and interfaces. Some evolutionary changes lead to a situation that requires different versions of a class or an interface to coexist in one application. This is difficult to do in Java, where two classes or interfaces with the same name cannot coexist unless loaded by unrelated classloaders. In this paper, we describe how to deal with such a situation without resorting to an elaborate naming scheme or an elaborate classloader hierarchy, which we show to be worse than our solution. 2 Petr Hnětynka, Petr Tůma In section 2, we detail the context in which the name clashes appear and explain why some of the apparently straightforward solutions such as naming schemes or classloader hierarchies do not work well. In section 3, we outline our approach to avoiding the name clashes and illustrate it on a proof of concept implementation. In section 4, we show how our approach differs from that of other Java component systems. Section 5 concludes the paper. 2 2.1 Java Component Systems Components, Classes, Interfaces A software component is an independent unit in a sense that its interface, which includes both the offered services and the context dependencies, is specified explicitly. This facilitates an easy reuse of components without knowledge of their internals. In contemporary Java component systems [2,6,13,15], the offered services are represented by a set of interfaces that a component provides, and the context dependencies are represented by a set of interfaces that a component requires. Given the provided and required interfaces, a component is represented by a set of classes that implement the provided interfaces and call the required interfaces. Besides being implemented directly as a set of Java classes, a component can also be composed from other components. We term these components composed, as opposed to primitive. A composed component is created by connecting other components through their provided and required interfaces. The connections can be described in an architecture description language, which will then provide information necessary for generating parts of the composed component implementation such as component bindings and factories. The composition of components forms a hierarchical structure that can be represented as a tree or, in a component system that allows sharing of components, an acyclic graph. 2.2 Sources of Name Clashes By a name clash, we understand a situation where a class or an interface cannot be loaded by the Java virtual machine because its name is the same as a name of another class or interface that has already been loaded. The name clashes that are of interest to us are related to the evolutionary changes that can occur during the lifecycle of a component application, namely changes to classes that make up the component and changes to interfaces that make up the component.1 The first source of name clashes is a situation where the classes that make up a component are replaced by a new version while the component application that uses 1 For sake of brevity, we omit the discussion of the likelihood that the situations described here as sources of name clashes will occur in practice. For readers that will not find the practicality of the situations evident, good arguments can be found in [1,9,11,14]. Managing Class Names in Java Component Systems with Dynamic Update 3 the component is running. This can lead to a name clash between the old and the new version of the classes. As an example, consider a component with a class LoggerImpl that is to be updated. 2 An attempt to replace this class by a new version will cause a name clash because the Java virtual machine will refuse to load the new LoggerImpl class after the old one has been loaded. The second source of name clashes is a situation where the interfaces that make up the component are replaced by a new version but the component application that uses the component does not change and an adapter is used to bridge the mismatch. This can lead to a name clash inside the adapter, which needs to access both the old and the new version of the interfaces. As an example, consider a component with an interface Log that is to be updated. An attempt to create an adapter between the old and the new version will cause a name clash because both versions of the interface will use the same name. The third source of name clashes is a situation where a single Java virtual machine is used to run several component applications to avoid redundant loading of shared code. This can lead to a name clash when the component applications use different versions of the same component. 2.3 Straightforward Solutions Do Not Work Well Unique names: Probably the most straightforward solution that helps to avoid the name clashes is to use unique names for each version of the classes and interfaces that make up a component. The obvious disadvantage of this approach is the need to change the names with each new version of the class and the interface. This disadvantage is further aggravated when the change is not confined to the component itself, but also needs to be made in the component application that uses the component. The use of unique names also leads to using longer names with prefixes or suffixes, which inconveniences the programmer. Different classloaders: Another straightforward solution is to use different classloaders to load classes and interfaces of different components. Each classloader is associated with a namespace and can therefore load a class or an interface even when another class or interface with the same name was already loaded by a different classloader. Using a distinct classloader for each component will therefore avoid name clashes because the names of classes and interfaces that make up each component will belong to a different namespace. Unfortunately, this solution makes the Java virtual machine see the interfaces of different components as incompatible even when they have the same definition. This is a problem when the components are to be connected to each other, because it is impossible to connect the incompatible interfaces directly through Java references. Java reflection: One way to avoid the problem of connecting seemingly incompatible interfaces is to use the Java reflection API. The references used to connect the interfaces can be passed as of the java.lang.Object type rather than their most 2 For more details on the components considered in the examples, see section 3.2. 4 Petr Hnětynka, Petr Tůma derived type, and the Java reflection API can be used to invoke methods on the interfaces. To invoke a method through the Java reflection API, an instance of the java.lang.reflect.Method class must be obtained to represent the method, and all arguments of the method that are of primitive types must be stored in corresponding wrapper classes. Although the code to do this can be generated from the interface description and therefore be no burden to the programmer, it slows down the invocations considerably. Table 1 illustrates this by comparing the times of a direct method invocation with the times of a method invocation through the Java reflection API on a dual CPU 360MHz UltraSparc machine with Java 1.4. no arguments Direct invocation Java reflection 11.22µs 872.4µs single String argument 11.22µs 803.3µs five int arguments 22.35µs 1643µs Table 1. Times of method calls Hierarchy of classloaders: Another way to avoid the problem of connecting seemingly incompatible interfaces is to use a hierarchy of classloaders. A classloader has exactly one parent, which is set during its creation and cannot be changed. Typically, a classloader delegates requests to load a class or an interface to its parent and only if the parent cannot load the class or the interface, the classloader tries to load it by itself. Classloaders that share a parent therefore also share parts of their associated namespaces. In addition to using different classloaders to load classes and interfaces of different components, the classloaders can be set up so that whenever two components are to be connected to each other, the loading of their interfaces will be delegated to the same parent classloader. The resulting hierarchy of classloaders will mirror the hierarchy formed by the composition of components. This solution makes the Java virtual machine see the interfaces of components that are to be connected to each other as compatible, because they were loaded by the shared parent classloader. Unfortunately, even this solution fails when the interfaces that make up a component are replaced by a new version and an adapter is used to bridge the mismatch between the component and the component application that uses the component. The interfaces that make up the adapter necessarily belong to a single namespace and because the adapter needs to access both the old and the new version of the interfaces, a name clash will occur. Modified Java virtual machine: Albeit not so straightforward, the third solution to be mentioned is to modify the Java virtual machine. This solution is used by some systems described in section 4. Without going into detail, we just note that this solution requires the component application to run on a nonstandard Java virtual machine and may interfere with mechanisms such as just in time compilation, which makes it generally less flexible. Managing Class Names in Java Component Systems with Dynamic Update 3 3.1 5 Removing Name Clashes in Byte Code Byte Code Manipulation The Unique names solution from section 2.3, which rested in using unique names for each version of the classes and interfaces that make up a component, is attractive in that it does not require changes to the Java virtual machine, does not incur performance penalty, and works for all the sources of name clashes considered in section 2.2. The problem rests with the convenience of its usage. Ideally, the programmer should be able to use the same names for different versions of the classes and interfaces, but the executable code of the component application should use unique names for each version. A solution to this problem rests with byte code manipulation of the executable code of the component application. Several tools for byte code manipulation are available [3,4,5,17]. Usually, they are used for performance optimizations, for extending functionality by intercepting method invocations, or for generating code in adaptable systems. In our solution, the byte code manipulation is used to augment the names of classes and interfaces that make up a component by adding a unique version identifier, and to rename the references to these classes and interfaces to use the augmented names. That way, the programmer can use the same names for different versions of the classes and interfaces in a convenient way, without running into name clashes. With this solution, an associated problem appears when the standard classloader of the Java virtual machine is used to load the classes or interfaces whose names were augmented by the byte code manipulation. The standard classloader expects the files that contain the classes and interfaces to be named the same as the classes and interfaces themselves, which means that renaming and repackaging of the files is also required. Alternatively, remote loading of classes from a version aware class server can be used. 3.2 Proof of Concept As a proof of concept, we have implemented the approach to avoid name clashes through byte code manipulation in SOFA [15,7]. SOFA is a project of our research group that provides a platform for component applications that supports a construction of hierarchic components connected by potentially complex connectors. The components are described in a component description language (CDL), which can include a description of the component behavior by protocols [16]. The components are fully versioned [12] and can be updated while the component application that uses them is running. In SOFA, the components are stored in a template repository together with the necessary metadata that describe their classes and interfaces, including versions. When running, the components reside inside a deployment dock, which provides the necessary deployment and execution facilities. A single deployment dock can run 6 Petr Hnětynka, Petr Tůma several component applications. Implemented in Java, it is therefore a platform where all the sources of name clashes that were outlined in section 2.2 can occur. When a component application is being launched in SOFA, the classes and interfaces are loaded by the deployment dock using a single classloader. To obtain the byte code of the classes and interfaces, the classloader contacts the template repository, which acts as a class server for the deployment dock. The template repository looks up the classes and interfaces by the names used in CDL. The names of the classes and interfaces in the byte code are then augmented using the ASM tool [3] and the augmented version is sent to the deployment dock. The augmentation is done using the ASM tool [3]. The only exception to this mechanism is the case of components that serve as adapters and therefore use several versions of the same interface. In this case, the programmer has to use different names for different versions of the interface simply to be able to write the adapter. The template repository therefore requires an additional translation table that maps the names used by the programmer to the augmented names. The ASM tool uses the visitor pattern for byte code manipulation. The template repository implements a class visitor with the visit, visitField, visitMethod and visitInnerClass methods. The visit method is called once for each visited class and augments the name of the class and the references to the parent class and the implemented interfaces. The visitField, visitMethod and visitInnerClass methods are called once for each field, method and inner class of the visited class. The visitMethod method instantiates a method visitor to modify the byte code of the visited methods. The method visitor iterates through all instructions of a method and augments the type names in all instructions that refer to type names. Similar to the class visitor, the code visitor has a separate method for visiting related groups of instructions. The instructions that need to be augmented are handled by the visitFieldInsn (instructions for loading or storing a field of an object), visitLocalVariable (a local variable declaration), visitMethodInsn (an instruction that invokes a method), visitMultiANewArrayInsn (the multianewarray instruction), visitTryCatchBlock (an exception handling block) and visitTypeInsn (instructions that take a type descriptor as an argument) methods. As an example, consider a primitive component of type Logger with a single provided interface of type Log for logging events. The component has the following CDL description: struct Event { … }; interface Log { void log (Event e); }; frame Logger { provides: Managing Class Names in Java Component Systems with Dynamic Update 7 Log log; }; architecture CUNI Logger implements Logger primitive; As the CDL description is compiled, the symbols in it are assigned a unique version identifier. In SOFA, a version identifier is globally unique, constructed from the name of the SOFA node and a sequence number. In our example, let us presume that the particular version identifier assigned to the symbols is nenya!0. As a next step of the compilation, a Java mapping for the described types and a component factory is generated. In our example, the generated interface is Log and the generated classes are Event and LoggerBuilder. When the CDL description is compiled and the mapping generated, the programmer provides the implementation of the component. In our example, it is the LoggerImpl class, which implements the Log interface. When writing the class, the programmer uses the same names as in the CDL description. Here is a fragment of the code of LoggerImpl: public class LoggerImpl implements Log { public void log (Event e) { ... } } When the component is launched, the template repository augments the names of all the classes and interfaces that make up the component by the version identifier, in this example nenya_E_0. 3 If the programmer had to achieve the same result without byte code manipulation, the fragment of the code of LoggerImpl above would have to look like this: public class LoggerImpl_nenya_E_0 implements Log_nenya_E_0 { public void log (Event_nenya_E_0 e) { ... } } To extend our example further, let us presume that during the lifecycle of the component application that uses the Logger component, another method will be added to the Log interface. The symbols in the CDL description of the new version will be assigned a new version identifier nenya!1. When providing the new implementation of the component, the programmer will simply add the new method to the old LoggerImpl class, without modifying the names of the classes and 3 The exclamation mark in nenya!0 is replaced because it is not allowed in class names. 8 Petr Hnětynka, Petr Tůma interfaces. When the new component will be launched, the template repository will augment the names of all the classes and interfaces by the version identifier nenya_E_1, which will make them different from the names used by the old component and therefore avoid name clashes. 4 Related Work Related to our work are the systems that address the need for coexistence of several versions of a class or an interface in a single Java virtual machine, or the need to dynamically update a Java application. One such system is described in [14], where the authors provide a dynamic environment for distributed Java applications that supports hosting multiple applications within a single JVM. The environment manages classes and objects in distinct class and object spaces, which allows hosting multiple applications without running into name clashes. The disadvantage of the system is that the applications must communicate with each other using only the types from the core of the system. The Java Distributed Runtime Updating Management System (JDRUMS) [1] allows updating Java classes at runtime. The name clashes are removed by using a modified Java virtual machine that can load a new version of a class with the same name as the old version. The first version of JDRUMS allows only limited changes of classes and objects. The second version, JDRUMS 2, provides more options for dynamic changes, but also brings security problems as during the update, it is possible to retrieve secret information that is not available during normal program execution. JDRUMS also disables the just in time compilation. The Dynamic Java Classes (DJC) [11] also allow updating Java classes at runtime. Again, the name clashes are removed by using a modified Java virtual machine with a dynamic classloader that allows a class to be defined multiple times. DJC also extend the Java security mechanism to cope with dynamic updates, and disable the just in time compilation. Many other Java component systems that exist today simply do not support the dynamic updates or coexistence of several versions of a class or an interface with the same name. One such system is the Enterprise Java Beans (EJB) framework from Sun [6]. The components in EJB are called enterprise beans or beans for short. There is no support for dynamic updates of beans in EJB. Fractal [2] from ObjectWeb is a general software composition framework that supports components. Fractal supports an explicit definition of provisions and requirements of a component, composed components with a formal architecture description, component bindings, component sharing and other features. Fractal itself is an abstract framework and serves as a base for a reference implementation called Julia. Fractal and Julia support dynamic updates through reconfiguration, which can add new components, change bindings and remove old components, but cannot handle dynamic updates that involve different versions of the same classes or interfaces. Managing Class Names in Java Component Systems with Dynamic Update 5 9 Conclusion In this paper, we have pointed out the problem of name clashes that occur in Java component systems because of evolutionary changes during the lifecycle of a component application, and explained the sources of these name clashes. We have shown that the standard facilities of the Java type system do not provide for a satisfactory solution, and explained how solutions such as naming schemes or classloader hierarchies may lead to performance penalties and inconvenience the programmer. We have proposed a solution to the problem of name clashes based on administering the names of classes and interfaces with a version identifier using a byte code manipulation tool. Through a proof of concept implementation, we have also demonstrated that our solution integrates smoothly with a Java component system. We have shown that our solution differs from and is superior to the solutions used in contemporary Java component systems. Acknowledgements The authors would like to thank František Plášil, Vladimír Mencl and Jiří Adámek for valuable comments. This work was partially supported by the Grant Agency of the Czech Republic in grants number 102/03/0672 and 201/03/0911. References 1. Andersson, J.: A Deployment System for Pervasive Computing, Proceedings of the International Conference on Software Maintenance (ICSM'00), 2000 2. Bruneton, E., Coupaye, T., Stefani, J. B.: The Fractal Composition Framework, http://www.objectweb.org/fractal/current/Fractal1.0-0.pdf 3. Bruneton, E., Lenglet, R., Coupaye, T.: ASM: A code manipulation tool to implement adaptable systems, http://www.objectweb.org/asm/current/asm-eng.pdf 4. Cohen, G. A., Chase, J. S., Kaminsky, D. L.: Automatic program transformation with JOIE, USENIX 1998 Annual Technical Conference, New Orleans, Louisiana, USA, 1998 5. Dahm, M.: Byte Code Engineering, Proceedings JIT'99, Springer, 1999 6. DeMichiel, L. G., Yalcinalp, L. U., Krishnan, S.: Enterprise JavaBeans Specification, Version 2.0, http://java.sun.com/products/ejb/docs.html 7. Distributed Systems Research Group: SOFA implementation in Java http://sofa.debian-sf.objectweb.org 8. Gosling, J., Joy, B., Steele, G., Bracha, G.: The Java Language Specification, Second Edition, http://java.sun.com/docs/books/jls 9. Lehman, M. M., Ramil, J. F.: Software Evolution in the Age of Component Based Software Engineering, IEE Proceedings Software, Special Issue on Component Based Software Eng., v. 147, n. 6, Dec. 2000, pp. 249-255 10. Lindholm, T., Yellin, F.: The Java Virtual Machine Specification, Second Edition, http://java.sun.com/docs/books/vmspec/index.html 10 Petr Hnětynka, Petr Tůma 11. Malabarba, S., Pandey, R., Gragg, J., Barr, E., Barnes, J. F.: Runtime support for type-safe dynamic Java classes, Proceedings of the European Conference on ObjectOriented Programming, June 2000 12. Mencl, V., Hnětynka, P.: Managing Evolution of Component Specifications using a Federation of Repositories, Tech. Report No. 2001/2, Dep. of SW Engineering, Charles University, Prague, Jun 2001 13. Object Management Group: CORBA Components, v 3.0, OMG document formal/0206-65 14. Paal, S., Kammüller, R., Freisleben, B.: Customizable Deployment, Composition, and Hosting of Distributed Java Applications, Proceedings of the International Symposium on Distributed Objects and Applications (DOA 2002), Springer, 2002 15. Plášil, F., Bálek, D., Janeček, R.: SOFA/DCUP: Architecture for Component Trading and Dynamic Updating, Proceedings of ICCDS'98, Annapolis, Maryland, USA, IEEE CS Press, May 1998 16. Plášil, F., Višňovský, S.: Behavior Protocols for Software Components, IEEE Transactions on Software Engineering, vol. 28, no. 11, Nov 2002 17. White, A.: Serp, http://serp.sourceforge.net