Developing applications

Prerequisite: Install and configure maven.

The example project basic-search-java contains a request / response processor that responds "Hello, World!" to requests. This is hence a stateless Vespa application with no data, to demonstrate how to build plugins for the Vespa JDisc container. Note that the Processor is only one of several available component types that can be deployed. To build, run:

$ mvn clean install
from the project root. Refer to the bundle plugin for build details.

In Vespa, configuration is accessed through generated config classes - read more in the Cloud Configuration System. The Maven target generate-sources generates the ExampleProcessorConfig class from src/main/resources/example-processor.def. This is invoked by mvn install.

As config classes are generated above, open the project in an IDE - IntelliJ IDEA supports opening pom.xml directly. If running IntelliJ on a Linux, configure the test environment.

Run

Run the application package:

$ mvn test -Dtest=ApplicationMain
Invoke the Processor:
$ curl http://localhost:8080/processing/
{"datalist":[{"data":"Hello, services!"}]}
The JDisc Container has a built-in handler at http://localhost:8080/ApplicationStatus. It returns information about the deployed application and its components, e.g. server ports and URI bindings for request handlers.

Unit test

Part of the generated application package is the integration test in src/test/java/com/yahoo/example/ApplicationTest.java. This test demonstrates how com.yahoo.application.Application can be used to run the JDisc Container in a JVM. Not only is this useful for testing, the same approach can be used in production on single-vm environments (e.g. grid jobs).

Notice the com.yahoo.application.container.JDisc class that is accessed by the test through app.getJDisc(clusterName). That class has methods for exercising all common JDisc component types.

Notice how the the test disables the network layer in the call to the Application factory method. Unit tests should disable the network to allow tests to be run in parallel. However, to have the JDisc Container bind to the appropriate network sockets, the network needs to be enabled.

Read more on unit testing configurable components.

Deploy

The above built Java code and ran it on a developer host. To deploy the application to a docker instance like in the quick start, build a deployable package:

$ mvn install package
This builds the application package target/application.zip. To deploy the application, read about application packages. When using the quick start, replace basic-search in the deploy-prepare stage with basic-search-java/target/application.zip, deploy and test the processor:
# curl http://localhost:8080/processing/

Next steps

Learn more about the APIs in the Vespa JDisc Container.

Troubleshooting

Cannot find *Config classes

Several projects depend on generated configuration classes. These are created by the generate-sources Maven target. The easiest way to avoid the problem is building the project with Maven from the shell, then importing in Eclipse/IntelliJ. If the project is already imported, first run the generate-sources target either from the IDE or the shell, then mark the pertinent target/generated-sources/vespa-configgen-plugin directories as source directories.

Maven

On error:

  at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:482)
  at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:482)
  at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:482)
  at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:482)
increase the maven thread stack size by export MAVEN_OPTS=-Xss2m

Running on Linux

IntelliJ does not pick up custom test profiles from pom.xml. If running IntelliJ on a Linux, configure its test environment with a reference to the library path (i.e. $VESPA_ROOT/lib64). The simplest way to do this is to edit the default configuration for JUnit.

Ignored provider

This it is usually a packaging problem:

A provider [the class] registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider [the class] will be ignored.
The JAX-RS classes, like the @GET annotation, have been bundled inside the plug-in bundle, causing the container not to recognize them as valid annotations. The fix is using mvn dependency:tree to figure out what dependency has caused JAX-RS to be included, and then add the proper exclusion clause to pom.xml.

No result returned when throwing WebApplicationException

The container has no special handling of javax.ws.rs.WebApplicationException and its subclasses. All Jersey plug-ins must either be exception safe, or supply the proper implementations of javax.ws.rs.ext.ExceptionMapper for all exceptions which are expected to be thrown from the plug-in (and annotate these with @Provider).

Basic testing

Above is a test that instantiates the Container and lets you run requests using a browser or HTTP client, by enabling the network interface:

public class ApplicationMain {

  @Test
  public static void main(String[] args) throws Exception {
    try (com.yahoo.application.Application app = com.yahoo.application.Application.fromApplicationPackage(
        FileSystems.getDefault().getPath("src/main/application"),
        Networking.enable)) {
      app.getClass(); // throws NullPointerException
      Thread.sleep(Long.MAX_VALUE);
    }
  }
}
Run/debug this, and it will invoke the plugins as configured. Most often, you will write tests like:
import java.io.File;
import org.junit.Test;
import com.yahoo.application.Application;
import com.yahoo.search.Query;
  
public class ApplicationTest {
  
  @Test
  public void testContainerApplicationWithReferencedContentCluster() throws Exception {
    try (Application application =
        Application.fromApplicationPackage(new File("myapplication/"),
        Networking.disable) ) {
      byte[] response = application.getJDisc("foo").search().processAndRender(chainId, rendererId,
      new Query("?query=foo"));
      ...
    }
  }
}
By disabling the network interface, you eliminate port conflicts, and you can run tests in parallel.