Resource Menu


Objectives

In order to develop modular and dynamic Java applications for some of our projects at Artenum, we have decided to use the OSGi framework.

Internal note: https://code.artenum.com/internal/wiki/-/wikis/dev/How-to-compile-VTK/

The OSGi framework is a module system and service platform for the Java programming language that implements a complete and dynamic component model. Source

OSGi is already successfully used by Eclipse or Glassfish v3 for instance. The main advantage of OSGi is that it provides an easy way to build component-based applications that can be dynamically loaded and updated at runtime. While creating an OSGi bundle is rather simple, when dealing with native libraries, things tend to become much more complicated, especially because there is very little documentation available on the subject.

The main objective of this (very long, sorry about that) article is to explain the specificities of MacOSX and the different issues Jérémie and myself faced while creating an OSGi bundle for VTK, the open source Visualization Toolkit, written in C++.

This article is divided in four parts:

  • 1) Introduction regarding JNI, dynamic linking on MacOSX and native libraries in OSGi, explains some particularities of OSX and OSGi regarding the use of native code in Java
    • a) JNI
    • b) Dynamic linking
    • c) OSGi and native libraries
  • 2) Compilation of VTK with Java wrapping, details how to take into account those OSX-specific aspects to compile VTK
    • a) Prerequisites
    • b) OSX special actions
    • c) Compiling VTK
    • d) Removing the version numbers
    • e) Downloading the result
  • 3) Creating a simple Java application using VTK, demonstrates the basic usage of VTK from a Java application
  • 4) Embedding VTK in an OSGi bundle, explains how to package VTK as an OSGi bundle
I hope this will help whoever wants to try integrating VTK in a generic OSGi application or in an Eclipse RCP application.

1) Introduction regarding JNI, dynamic linking on MacOSX and native libraries in OSGi

a) JNI

When you want to use a native library such as VTK in a Java application, one of the possibilities is to use JNI, the Java Native Interface, to allow the communication between the Java code running in the JVM and the native library. The details on how to create the native dynamic libraries and how to access them from Java code are out of the scope of this article, but you can find a very simple example about how to use JNI here.

What is important to know here is that to use native code in a Java application, one needs:

  1. The dynamic libraries compiled from the native code (.dylib or .jnilib files on OSX, .so files on Linux, .dll files on Windows)
  2. Java classes declaring the native functions to be used
  3. A way to indicate the JVM where dynamic libraries are located, as it will load them dynamically at runtime (hence the name)
Hopefully, VTK provides CMake scripts to perform the Java wrapping of the library, i.e. generating the dynamic libraries and the Java classes to access them (items 1 and 2).

For item 3, on OSX, there are two ways to indicate the location of the dynamic libraries to the JVM when executing the application:

  • The first one is to use the system environment variable DYLD_LIBRARY_PATH (on Linux, it's LD_LIBRARY_PATH, on Windows PATH):
> export DYLD_LIBRARY_PATH=path/to/my/libs
> java -jar myApplication.jar
  • The second method is to use the java.library.path Java property to indicate the libraries path.
> java -Djava.library.path=path/to/my/libs -jar myApplication.jar

b) Dynamic linking

This is where we have to go a little deeper on how MacOSX actually deals with dynamic linking and how does the dynamic linker know where to find the libraries.

As very well explained in this article http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html, MacOSX uses the "install name" to find libraries:

An install name is just a pathname embedded within a dynamic library which tells the linker where that library can be found at runtime. For example, libfoo.dylib might have an install name of /usr/lib/libfoo.dylib. This install name gets copied into the application at link time. When the dynamic linker goes looking for libfoo.dylib at runtime, it will fetch the install name out of the application and know to look for the library in /usr/lib. Source

To see the install name of a library, you can use the otool command. It will show the install name as well as the dependencies of your library. For instance, here is the result of the command on one of VTK dynamic library, generated by the default Java wrapping in VTK:

> otool -L libvtkCommonJava.5.6.dylib 
libvtkCommonJava.5.6.dylib:
	libvtkCommonJava.5.6.dylib (compatibility version 5.6.0, current version 5.6.1)
	libvtkCommon.5.6.dylib (compatibility version 5.6.0, current version 5.6.1)
	libvtksys.5.6.dylib (compatibility version 5.6.0, current version 5.6.1)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0)
	/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)

The install name of the library is the one displayed on the first line "libvtkCommonJava.5.6.dylib".

This library depends on the libraries "libvtkCommon.5.6.dylib", "libvtksys.5.6.dylib", "/usr/lib/libSystem.B.dylib" and "/usr/lib/libstdc++.6.dylib". You can see that the two last dependencies are given with an absolute path, while the first ones have no path specified. This is considered as a relative path, such as "./libvtkCommonJava.5.6.dylib".

And here is the problem with MacOSX: where is "."?

When the DYLD_LIBRARY_PATH environment variable is used, it seems that OSX considers "." as the path indicated in the environment variable, so when using this method, the java application using VTK will work properly.

When using the java.library.path property however, OSX considers "." as being the directory where the application is launched. This will thus only work if the dynamic libraries are at the same place as the executable.

It is exactly the same as using @executable_path, as explained in the article http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html, which is not handy.

Starting in 10.4, Apple provided @loader_path which does what you want here. It expands to the full path, minus the last component, of whatever is actually causing the target library to be loaded. If it's an application, then it's the same as @executable_path. If it's a framework or plugin [or a dynamic library], though, then it's relative to that framework or plugin, which is much more useful. Source

This is why when compiling VTK with Java wrapping, one needs to modify the library path to be, for instance:

libvtkCommonJava.dylib:
	@loader_path/libvtkCommonJava.5.6.dylib (compatibility version 5.6.0, current version 5.6.1)
	@loader_path/libvtkCommon.5.6.dylib (compatibility version 5.6.0, current version 5.6.1)
	@loader_path/libvtksys.5.6.dylib (compatibility version 5.6.0, current version 5.6.1)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0)
	/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)

MacOSX has a tool, called install_name_tool that can modify the install name (with -id option) or the dependencies names (with -change option). We will see how to use it for VTK in part 2.

c) OSGi and native libraries

The OSGi framework dynamically loads and updates Java components that are called bundles.

In its most common form, a bundle is simply a JAR file whose manifest has OSGi-specific entries*. This means that to distribute the native libraries inside the bundle and to benefit from OSGi dynamism, one needs to add the libraries in the jar file.

The JVM cannot however link Java code to library files contained in a JAR file. This is why, at runtime and on request, the OSGi framework is going to unpack the required native libraries in a temporary folder.

Unfortunately, OSGi can only extract .jnilib files from the bundle and ignores .dylib files. This is very annoying, as by default, VTK Java wrapping creates .dylib library files. We will see in the next part of this article how to solve this problem and obtain, for example:

libvtkCommonJava.jnilib:
	@loader_path/libvtkCommonJava.5.6.jnilib (compatibility version 5.6.0, current version 5.6.1)
	@loader_path/libvtkCommon.5.6.jnilib (compatibility version 5.6.0, current version 5.6.1)
	@loader_path/libvtksys.5.6.jnilib (compatibility version 5.6.0, current version 5.6.1)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0)
	/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)

Moreover, the OSGi framework extracts those libraries one by one, when asked by a System.loadLibrary() call. This means that if we load the previously mentioned vtkCommonJava library without having explicitly loaded vtkCommon and vtksys, they will NOT be present in the temporary folder and OSGi will throw an UnsatisfiedLinkError.

This is not very handy (to say the least), as we need to manually call the System.loadLibrary() for all shared library in the order of their dependencies. The otool command is very useful at this point.

There is a last problem. As shown earlier, the dependencies created by the default VTK Java wrapping explicitly specify the version number of the libraries. And as just stated in the previous paragraph, OSGi will NOT extract libvtkCommon.5.6.jnilib for instance unless we explicitly call System.loadLibrary(vtkCommon.5.6). Thus calling System.loadLibrary(vtkCommonJava) will throw an UnsatisfiedLinkError. In order to fix this last linking problem, we should modify the dependencies to not specify the version number required, such as:

libvtkCommonJava.jnilib:
	@loader_path/libvtkCommonJava.jnilib (compatibility version 5.6.0, current version 5.6.1)
	@loader_path/libvtkCommon.jnilib (compatibility version 5.6.0, current version 5.6.1)
	@loader_path/libvtksys.jnilib (compatibility version 5.6.0, current version 5.6.1)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0)
	/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)

We will explain in next part how to achieve this. Of course, it will then complicate the concurrent use of several versions of VTK, but in my humble opinion, this is not something I would recommend anyway.


* It is also possible in OSGi to load directly a folder (containing the Manifest with OSGi-specific attributes), without requiring to compress the bundle in a jar file. This saves some time when using native libraries as it is not necessary to unpack the native libs at runtime, but it uses more disk space, as jar file are compressed, and is less practical for distribution.


2) Compiling VTK for OSX with Java wrapping

a) Prerequisites

Now that we have seen the constraints on the Java wrapping imposed by MacOSX, it is time to see how to adapt CMake scripts to generate .jnilib libraries with @loader_path attributes.

First, you need to download CMake, the cross-platform build tool and VTK latest source code.

When you first launch CMake, it will ask you the path of the source code and the path of the build directory in which it is going to generate all CMake scripts necessary to compile VTK. Then, when you click on the "configure" button, it will ask you to choose the generator for the project. As illustrated on figure below, choose Unix Makefiles with default native compilers.

You will then have to choose the settings of installation, as shown on figure below:

Click on the "configure" button and then "generate".

b) OSX Special actions

Depending on the version of VTK, special actions are required to make vrk compatible with OSGi (as explained in the first section).

  • In the latest versions of VTK (> 6.3.0), there is a VTK_JAVA_INSTALL option. If we select this option, the dynamic librairies are copied to the destination folder and jars are created there. The folders lib, bin and include are not created. This option also changes the extension of the dynamic libraries from .dylib to .jnilib.
  • On previous versions of VTK (< 6.3.0), you need to perform the following actions :
Changing .dylib extensions to .jnilib

With the Terminal application, go to the root folder of the build directory. We are now going to you sed command to replace all .dylib extensions by .jnilib extensions. Use the changeLibExtensions.sh script:

Nota Bene: there are no double backslashes in the sed commands, its the wiki system I use that does not want to write a single backslash, so DO NOT copy/paste the following lines or replace the double backslashes by single ones. The double slash // is correct however. The best solution is to download the sh file directly

> grep -r -H ".dylib" .|awk -F':' '{print $1}'|sort|uniq|grep -v "^Binary"|grep -v "CMakeCache.txt">dylibFiles.txt
> cat dylibFiles.txt | xargs sed -i "" 's/\\.dylib/\\.jnilib/g'

Inserting the @loader_path attribute in dynamic library dependencies

We know have to insert the @loader_path attribute in the installation script. Use the addLoaderPath.sh script:

Nota Bene: there are no double backslashes in the sed commands, its the wiki system I use that does not want to write a single backslash, so DO NOT copy/paste the following lines or replace the double backslashes by single ones. The double slash // is correct however. The best solution is to download the sh file directly

> grep -r -H '\\-change' .|awk -F':' '{print $1}'|sort|uniq|grep -v "^Binary"|grep -v "$~"|grep -v "addLoaderPath.sh">changeLib.txt
> cat changeLib.txt | xargs sed -i "" 's/id "/id "@loader_path\\//'
> cat changeLib.txt | xargs sed -i "" 's/-change "\\(.*\\)" "\\(.*\\)"/-change "\\1" "@loader_path\\/\\2"/'

c) Compiling VTK

We just have now to compile and install VTK (depending on your machine this may take one to two hours, so be patient )

> make -j 2
> make install

The "-j 2" option is set to use two processors on my machine.

d) Removing the version numbers

To remove the version numbers, I have created a small script called copyNoVersion.sh.

To use it, first create a new empty directory and execute it with the command:

./copyNoVersion $JNILIB_FOLDER

where $JNIFOLDER is the folder containing the jnilib files you just created. The script copies the required jnilib files (not the symbolic links), changes their name to remove the version number and then updates the dependencies with the otool command to remove the version numbers as well.

e) Download the result

If you don't want to go through all these steps and just would like the result directly, you can download it for VTK 5.6.1 here: http://maven.artenum.com/content/groups/public/vtk/vtk-native/5.6.1/vtk-native-5.6.1-osx64b.zip.

The CMakeCache.txt file I used to compile and wrap this version of VTK is here: CMakeCache.txt

3) Creating a simple Java application using VTK

Now that we have a compiled version of VTK, lets create a concrete example showing how to use VTK from a pure Java application (no OSGi yet). This sample application will display a 3D cone in a JFrame.

The source code (together with the compiled VTK lib) can be download here : test-vtk.zip.

In this project, there are two classes:

  • org.test.vtk.TestVTK is a JFrame that contains one method start() to initialize a vtkPanel containing the 3D cone.
  • org.test.vtk.Start is the class that contains the main(String[] args) method that will be executed on startup.
I have used Maven for the project compilation and packaging, so if you want to generate the executable jar file, you will need Maven installed on your machine. Then, in the Terminal application, go to the root folder of the project and type:

mvn clean package

This should create a "target" folder containing two jar files. What we are interested in is the test-vtk-2.0-SNAPSHOT-jar-with-dependencies.jar file that contains both the project classes and the VTK java classes.

Now, lets execute the code. As explained in part 1, on OSX, there are two ways to execute a Java application using native libraries:

  • The first one is to use the system environment variable DYLD_LIBRARY_PATH. This is what is done in the runSysEnv.sh script placed in the project root folder.
export DYLD_LIBRARY_PATH=./dependencies/thirdparty/vtk/vtk-5.6.1/osx64b/
java -jar target/test-vtk-2.0-SNAPSHOT-jar-with-dependencies.jar
  • The second method uses the java.library.path Java property to indicate the JVM where to find the libraries. This is what is done in the runJavaProp.sh script placed in the project root folder.
java -Djava.library.path=./dependencies/thirdparty/vtk/vtk-5.6.1/osx64b/ -jar target/test-vtk-2.0-SNAPSHOT-jar-with-dependencies.jar

If you execute either of the provided scripts, you will see a beautiful 3D cone.

If we would have used the default VTK Java wrapping, only the first one would have worked, while the second one would have thrown an UnsatisfiedLinkError.

4) Embedding VTK in an OSGi bundle

Now that we have seen how to create a Java project using VTK on OSX, we can try to create a VTK OSGi bundle.

In this project, we have created two classes:

  • org.test.vtk.osgi.TestVTK is the same JFrame that contains the method start() to initialize a vtkPanel containing the 3D cone. It also contains the stop()method called when the bundle is stopped.
  • org.test.vtk.osgi.Activator is the BundleActivator class that will be called by OSGi when the bundle is started or stopped. It statically loads the VTK libraries one by one, in the correct order of their dependencies, as shown below:
static {
	System.loadLibrary("vtkproj4");
	System.loadLibrary("vtkalglib");
	System.loadLibrary("vtksys");
	System.loadLibrary("vtkCommon");
	System.loadLibrary("vtkFiltering");
	System.loadLibrary("vtkexpat");
	System.loadLibrary("vtkjpeg");
	System.loadLibrary("vtkzlib");
	System.loadLibrary("vtklibxml2");
	System.loadLibrary("vtktiff");
	System.loadLibrary("vtkpng");
	System.loadLibrary("vtksqlite");
	System.loadLibrary("vtkmetaio");
	System.loadLibrary("vtkNetCDF");
	System.loadLibrary("vtkDICOMParser");
	System.loadLibrary("vtkNetCDF_cxx");
	System.loadLibrary("vtkIO");
	System.loadLibrary("vtkImaging");
	System.loadLibrary("vtkverdict");
	System.loadLibrary("vtkGraphics");
	System.loadLibrary("vtkfreetype");
	System.loadLibrary("vtkftgl");
	System.loadLibrary("vtkGenericFiltering");
	System.loadLibrary("vtkRendering");
	System.loadLibrary("vtkexoIIc");
	System.loadLibrary("Cosmo");
	System.loadLibrary("VPIC");
	System.loadLibrary("vtkParallel");
	System.loadLibrary("vtkHybrid");
	System.loadLibrary("vtkWidgets");
	System.loadLibrary("vtkVolumeRendering");
}

We can execute the bundle in an OSGi service platform implementation (we chose Equinox, but it also works with Felix).

# First we delete the cache folder contraining previously loaded bundles
rm -rf configuration/org.eclipse.osgi configuration *.log
# Then we execute Equinox with the argument -Xcheck:jni to see JNI-related errors
java -Xcheck:jni -jar org.eclipse.osgi_3.6.0.v20100517.jar -console
osgi> install file:/Users/ben/Documents/workspace/test-osgi-vtk/target/test-osgi-vtk-2.0-SNAPSHOT.jar
osgi> start 1

But here, we end up with an error:

WARNING in native method: JNI call made with exception pending
	at vtk.vtkPanel.RenderCreate(Native Method)
	at vtk.vtkPanel.Render(vtkPanel.java:166)
	- locked <1060ffa88> (a vtk.vtkPanel)
	at vtk.vtkPanel.paint(vtkPanel.java:189)
	at sun.awt.RepaintArea.paintComponent(RepaintArea.java:276)
	at sun.awt.RepaintArea.paint(RepaintArea.java:241)
	at apple.awt.ComponentModel.handleEvent(ComponentModel.java:263)
	at java.awt.Component.dispatchEventImpl(Component.java:4790)
	at java.awt.Component.dispatchEvent(Component.java:4544)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:635)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
FATAL ERROR in native method: Bad global or local ref passed to JNI
	at vtk.vtkPanel.RenderCreate(Native Method)
	at vtk.vtkPanel.Render(vtkPanel.java:166)
	- locked <1060ffa88> (a vtk.vtkPanel)
	at vtk.vtkPanel.paint(vtkPanel.java:189)
	at sun.awt.RepaintArea.paintComponent(RepaintArea.java:276)
	at sun.awt.RepaintArea.paint(RepaintArea.java:241)
	at apple.awt.ComponentModel.handleEvent(ComponentModel.java:263)
	at java.awt.Component.dispatchEventImpl(Component.java:4790)
	at java.awt.Component.dispatchEvent(Component.java:4544)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:635)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Invalid memory access of location 0x0 rip=0x1010af1f4
./ben.sh: line 11: 23453 Abort trap              java -Xcheck:jni -jar org.eclipse.osgi_3.6.0.v20100517.jar -console

The problem comes from the way OSGi bundle classloader manages package visibility (thank you Richard for your help on this issue: http://www.mail-archive.com/users@felix.apache.org/msg08864.html).

In standard Java applications, the Classloader makes all packages accessible by anyone. In a modular frameworks like OSGi, the idea is that each bundle has to declare what it needs (Import-Package) and what it provides (Export-Package). The framework then enforces these visibility constraints in order to build a loosely coupled module system.

In OSGi, when a package is not explicitly exported, it is not visible to others. And when it is not explicitly imported by a bundle, this one cannot access it. By default in OSGi, only java.* packages are available to anyone.

And this is what causes our error above: the RenderCreate native method requires apple.awt.ComponentModel to work properly. But the apple.awt package was not declared in the bundle Import-Package tag, so when the native code is called, it does not find it.

There are two ways to solve the problem:

  • In Felix/Equinox configuration settings, add the "org.osgi.framework.bootdelegation=apple.*" property. This tells OSGi that all apple.* packages are visible to all bundles. This works, but you somewhat lose the modularity of OSGi, as everyone can access those packages.
  • Explicitly import the package in the bundle manifest Import-Package entry. For OSGi to resolve this new dependency of your bundle, you can either:
    • add org.osgi.framework.system.packages.extra=apple.awt in Felix/Equinox configuration settings (your configuration is however now platform-dependent)
    • create a system bundle fragment that exports apple.awt (and contains no other files than the manifest)
I chose in my example application to implement the latter: a system bundle fragment, called apple.awt.1.0.0.jar is available at the root project. When running my OSGi framework, I just need to load the fragment bundle:

# First we delete the cache folder contraining previously loaded bundles
rm -rf configuration/org.eclipse.osgi configuration *.log
# Then we execute Equinox with the argument -Xcheck:jni to see JNI-related errors
java -Xcheck:jni -jar org.eclipse.osgi_3.6.0.v20100517.jar -console
osgi> install file:/Users/ben/Documents/workspace/test-osgi-vtk/apple.awt.1.0.0.jar
osgi> install file:/Users/ben/Documents/workspace/test-osgi-vtk/target/test-osgi-vtk-2.0-SNAPSHOT.jar
osgi> start 2

Note that we don't start the system fragment. It is not possible to start a fragment because it has no activator.

The source code of this sample project (together with the compiled VTK lib) can be download here : test-osgi-vtk.zip.

Conclusion and suggestions

As we have seen, we have successfully created a VTK OSGi bundle for OSX.

We have first reviewed how MacOSX linking works and what are the OSGi-specific constraints when dealing with native code.

We also saw how to solve these linking problems via a few "simple" shell scripts.

Then, we showed how to use VTK from a basic Java application, before embedding VTK in an OSGi bundle.

We are going to suggest VTK development team to integrate at least the jnilib extensions and the @loader_path to their CMake scripts to prevent the use of our pre-compilation scripts.

We are also going to suggest them not to version the jnilib files.

The next step of this work will be to create a cross-platform version of our VTK bundle. Here, we have developed a single bundle for OSX, but we would like to use VTK on the major platforms available (Windows, Linux, OSX) for 32 and 64 bits architectures. We will thus create a common VTK bundle and platform-dependent fragments that will be deployed depending on the target environment.



posted by Benoît Thiébault at Mar 29, 2011 3:19 PM
Quote
posted by Guest at Dec 30, 2010 12:54 PM
Quote
I'm new to vtk,and i have a question,when "import vtk.*",we should use vtk.jar, but i cann't find it in the download file?
posted by Benoît Thiébault at Nov 7, 2010 11:00 AM
Quote
Yes, I have the dll and so files for other platforms, you can download them here: http://maven.artenum.com/content/groups/public/vtk/vtk-native/5.6.1/
I have tested the Windows and MacOSX versions. The Linux version is not tested yet however. And I still have to generate the Linux 64bits version.

Also, for now, I just have an OSGi bundle on OSX. I still have problems on Linux (which I will share on my next post) and haven't tried windows yet.

posted by Guest at Nov 6, 2010 8:12 PM
Quote
Just awesome ! Great job !!I worked 5 five years ago with vtk and java and it was challenging to compile… 5 years later it is harder but still fantastic when it works.

I am starting a project using vtk and java, and I was wondering if you also have the dll/so for the others platform...

Again THANKS !

Arnaud

posted by Benoît Thiébault at Oct 26, 2010 9:55 AM
Quote
Please leave your comments here



Last edited by Arnaud Trouche at Jan 18, 2024 10:35 AM - Edit content - View history - View source