Virgo recipe "Getting Started - Uploading Files"

Virgo Homepage

Acknowledgements

This guide is inspired by "Getting Started - Uploading Files" from the Spring Guides.

Getting Started - Uploading Files

The original guide uses Spring Boot to bootstrap the demo application. This guide walks through the necessary additional steps/configuration to get "Getting Started - Uploading Files" up and running on Virgo.

Shopping list

Note

Only Virgo Server for Apache Tomcat and Virgo Jetty Server are supported by the Virgo Tooling.

Ingredients

Preparations

Get the code

Clone the code from the Git repository: Sample Git repository:

$ cd ~/MyAwesomeProjectDirectory/
$ git clone git://git.eclipse.org/gitroot/virgo/org.eclipse.virgo.samples.git
$ cd org.eclipse.virgo.samples
$ cd recipe-uploading-files

Once inside the source directory of this recipe - your are good to go.

Create the Custom Virgo Runtime

Create your own Virgo runtime with Gradle and Dockerizor:

$ ./gradlew :recipe-uploading-files-runtime dockerize

The custom Virgo Runtime is packaged as tar archive and available from the runtime build directory. Extract the runtime into some directory - let’s say into VIRGO_HOME.

Caution

You’ll need a Docker daemon running in order to create your custom Virgo runtime. For more information please refer to the Dockerizor documentation.

Create Eclipse Project Metadata

Create the Eclipse IDE metadata with:

$ ./gradlew eclipse

Prepare the Virgo Tooling

Download Eclipse IDE for Java EE Developers e.g. Eclipse Mars.2 (4.5.2).

Start your Eclipse IDE and add the Virgo Tooling to your Eclipse IDE.

Optionally add the Docker Tooling to your Eclipse IDE.

Import the code into your Eclipse IDE

Now you can menu:Import[Gradle > Gradle Project] into your IDE. The project folder is called recipe-uploading-files and lies directly in the "Sample Applications" folder you created previously with the Git clone command. (org.eclipse.virgo.samples).

Note

Before you can drag’n’drop the project onto your freshly created Virgo Server you might have to execute menu:Virgo[Run Generation] of META-INF/MANIFEST.MF (via a right click on the project) within your Eclipse IDE.

Create new Virgo Server Runtime

Define a new server

Within the IDE menu:New[Server > EclipseRT > Virgo Runtime] and choose the directory of the previously installed Virgo server VIRGO_HOME.

Directions

Virgo has no support for Springs @EnableAutoConfiguration. Thus you have to be explicit where Spring Boot configures the embedded Tomcat with reasonable defaults on your behalf. Last but not least Virgo is an OSGi runtime and every bundle needs to be explicit about its needs in the META-INF/MANIFEST.MF.

This is mostly about specifying necessary Import-Package and the Web-ContextPath the file upload application wants to be published.

template.mf
Bundle-Version: 0.1
Bundle-Name: Virgo Recipe uploading files
Bundle-SymbolicName: org.eclipse.virgo.samples.recipe.uploading
Bundle-Vendor: Eclipse Virgo
Import-Bundle: org.springframework.beans,
 org.springframework.context,
 org.springframework.core,
 org.springframework.web,
 org.springframework.webmvc
Import-Package: org.eclipse.virgo.web.dm
Web-ContextPath: /upload-guide
Bundle-Classpath: .

You can mix and match the Virgo specific header Import-Bundle and OSGi standard header Import-Package within the MANIFEST.MF. In the sample the fine-tuning of the MANIFEST.MF is done within the Bundlor template.mf.

Note
If you intend to run your bundles in different OSGi containers you have to limit yourself to the OSGi standard headers.

Next step is to confiugre the servlet container via WEB-INF/web.xml:

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">

    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/root-context.xml
        </param-value>
    </context-param>

    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.eclipse.virgo.web.dm.ServerOsgiBundleXmlWebApplicationContext</param-value>(1)
    </context-param>

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Processes application requests -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                /WEB-INF/spring/appServlet/servlet-context.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>
  1. Virgo specific contextClass

Note
Most important the contextClass which is not the default from the Spring Framework, but org.eclipse.virgo.web.dm.ServerOsgiBundleXmlWebApplicationContext.

Finally the Spring web application context. Nothing Virgo specific here:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">

	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<context:component-scan base-package="org.eclipse.virgo.samples.recipe.uploading" />

	<beans:bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
		<beans:property name="messageConverters">
			<util:list>
				<beans:bean id="byteArrayMessageConverter" class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
			</util:list>
		</beans:property>
	</beans:bean>

	<!-- Configure the multipart resolver -->
	<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<!-- one of the properties available; the maximum file size in bytes -->
		<beans:property name="maxUploadSize" value="50000000" />
	</beans:bean>

</beans:beans>

Let’s taste

For easy client-side consumption of the file upload you can use the FileUploader.

public class FileUploader {

    public static void main(String[] args) throws FileNotFoundException {
        if (args.length == 0) {
            System.out.println("Usage: Requires the name of a file to upload.");
            System.exit(1);
        }

        RestTemplate template = new RestTemplate();
        MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
        parts.add("name", args[0]);
        parts.add("file", new FileSystemResource(args[0]));
        String response = template.postForObject("http://localhost:8080/upload-guide/upload",
        			parts, String.class);
        System.out.println(response);
    }
}
Note
Please see the original guide "Getting Started - Uploading Files" for more background information about this client.

There is an easy way to use the FileUploader with Gradle:

$ ./gradlew run

Or if you are more used to curl and the command line simply run the following command within the project’s directory.

$ curl -F "name=sample.txt" -F "file=@org.eclipse.virgo.samples.recipe.uploading/sample.txt" http://localhost:8080/upload-guide/upload

Either way you should see the following output independent of your choice:

You successfully uploaded sample.txt into sample.txt-uploaded !

Dockerise the Application

We use Gradle and Dockerizor to build the application. Instead of running the sample within the Eclipse IDE you can build a Docker image:

$ ./gradlew dockerize

and run the application with:

$ docker run --rm -it --name uploading-files --publish 8080:8080 virgo-recipe/uploading-files-app