Sunday, December 28, 2014

EE Servlet 3: Getting started with web application module and a landing page

Getting started with Servlet 3

Web application module in Java EE is probably the most common type of application module that a developer would encounter and work on. That's because not only it can provide users the UI, but it also supoprt many common web application patterns: Model View Controller, Filter, Session, Context Listener, Http Request, Paramters, Query, and Form handling, Http Response writer, redirect, error etc. You can do all these with Servlet spec alone, so getting to know it well is an important part of learning in writing good web application.

Servlet has been around for a long time, and many developers are already familiar with it. There are many other web frameworks such as Tapestry or Spring MVC that are built on top of Servlet. These frameworks provide separate programming models that suppose to easy development process, but nontheless the core concept is still based on the Servlet technologies (or at least tightly integrated if it were to run by any web container server). In this post, I will try to highlight how to get a web module application started, and configure a typical need: a default landing page.

Hello World

Like many things in EE environment, you would write small components as Java class and then deploy them onto a server and let the server manage it's lifecycle and execution. So as with Servlet, you would write a simple Java class that implements Servlet interface, package it and deploy, and server will do it's magic.

Before Servlet 3.0, your servlet component is configured and mapped in web.xml file, but now you can just add an annotation directly on your servlet class and the app server should be able to automatically deploy and run it. Here is an example of a classic hello world.

package zemian.servlet3example.web;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("<!DOCTYPE html>");
        writer.println("<html>");
        writer.println("<body>");
        writer.println("<p>Hello World!</p>");
        writer.println("</body>");
        writer.println("</html>");
    }
}

In this Servlet, I simply extends an existing base HttpServlet class that should be available in all Serlvet spec. and in response to a http GET request, I write a Hello World message out as html response.

You may find above code in servlet3-example. Build and deploy it and then you can access it with http://localhost/servlet3-exmaple/hello. (I have many other servlet examples in the project, but you may just concentrate in this class for now.)

How to configure a default landing page in Servlet 3

A typical application server will likely default a landing page to "index.html" or "index.jsp" if it exists. For example, if I have written a IndexSerlvet class and mapped to "/index" instead, then you need to tell the server default to there. This will happen if users only type http://localhost/servlet3-example with context path in URL only.

Despite you can can do just about most things in Java annotations with Servlet 3.0 as equivalent to the content found web.xml file, but not the welcome file though. So to do this, you would still need to create this good old web.xml. Here is an example

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>
</web-app>


Above example will default the landing page to a Servlet url mapping with "/index" path.

TIPS: Do NOT to use "/" prefix when definining welcome-file element, else you will get a page not found error and likely your server won't even print any error message in the log!

Another alternate solution instead of overrite welcome-file is simply add a "index.jsp" file in root of webapp folder and do a redirect like this:

<% response.sendRedirect(request.getContextPath() + "/index"); %>


Monday, December 22, 2014

Developing Java EE applications with Maven, NetBeans and Glassfish

I have been working with EE 6 stack lately, and I find it quite pleasant and productive. For my own learning purpose, I intend to explore more deeper on some of the major components available on the EE stack.

I have started a java-ee6-examples project in GitHub, and I plan to add my examples and working demo code there, along with some blog posts whenever I can. The project is seperated into sub-modules that a typical EE application would organized: a parent module, a common library jar module and one or more web modules etc. The project is buildable using Maven 3 tool on command line, and you may use any major IDE that supports Maven (I will try out NetBeans for these demos).

I will also be testing my examples application mainly on Glassfish Server. Glassfish Server is an open source EE application server, and its current 4.x release supports EE 7 already (GF 3.x is for EE 6). We should able to run any EE 6 applications on GF 4.x without much problems, so for my learning purpose, I will restrict my examples to use EE 6 for now (you will notice that I have to set EE 6 version as dependency in Maven pom file!)

So if you are interested in these, watch this blog for future updates.

To help you started with EE development, I jot down few useful links here.

Downloads:
JDK 7
NetBeans IDE
Glassfish Application Server 
(Oracle also provides convenient package download that includes all 3 above!)

References:
EE 6 Tutorial
EE 6 Technologies
EE 6 API
JDK 7 API


Thursday, December 18, 2014

Writing your own logging service?

Application logging is one those things like favorite Editors war: everyone has their own opinions and there are endless of implemenations and flavors out there. Now a days, you likely would want to use something already available such as Log4j or Logback. Even JDK has a built in "java.util.logging" implementation. To avoid couple to a direct logger, many projects would opt to use a facade interface, and there is already couple good ones out there already, such as SLF4J or Apache Common Logging etc.

Despite all these, many project owners still want to try write their own logger service! I wondered if I were to ask and write one myself, what would it be like? So I played around and come up with this simple facade that wraps one of the logger provider (JDK logger in this case), and you can check it out here. With my logger, you can use it like this in your application:

 import zemian.service.logging.*;
 class MyService {
   static Log LOG = LogFactory.createLog(MyService.class);
   public void run() {
    
LOG.info(Message.msg("%s service is running now.", this));
   }
 }


Or you can use the Logger wrapper to avoid many imports:
 import zemian.service.logging.Logger;
 class MyService2 {
   static
Logger LOGGER = new Logger(MyService2.class);
   public void run() {
    
LOGGER.info("%s service is running now.", this);
   }
 }


Some principles I followed when trying this out:
  • Use simple names for different level of messages: error, warn, info, debug and trace (no crazy fine, finer and finest level names.)
  • Seperate Log service from implementation so you can swap provider.
  • Uses Message logging POJO as data encapsulation. It simplifies the log service interface.
  • Use log parameters and lazy format binding to construct log message to speed performance.
Do not go crazy with logging service implemenation make it complex. For example I recommend NOT to mix business logic or data in your logging if possible! If you need custom error codes to be logged for example, you can write your own Exception class and encapsulate there, and then let the logging service do its job: just logging.

Here are some general rules about using logger in your application that I recommend:
  •  Use ERROR log messages when there is reallyl a error! Try not to log an "acceptable" error message in your application. Treat an ERROR as critical problem in your application, like if it's in production, some one should be paged to take care of the problem immediately. Each message should have a full Java stacktrace! Some application might want to assign a unique Error Code to these level of messages for easier identification and troubleshoot purpose.
  • Use WARN log messages if it's a problem that's ignorable during production operation, but not good idea to supress it. Likely these might point to potentially problem in your application or env. Each message should have a full Java stacktrace, if available that is!
  • Use INFO log messages for admin operators or application monitors peoples to see how your application is doing. High level application status or some important and meaningful business information indicators etc. Do not litter your log with developer's messages and unessary verbose and unclear message. Each message should be written in clear sentence so operators knows it's meaningful.
  • Use DEBUG log messages for developers to see and troubleshoot the application. Use this for critical application junction and operation to show objects and services states etc. Try not to add repeated loop info messages here and litter your log content.
  • Use TRACE log message for developers to troubleshoot tight for loop and high traffic messages information.
  • You should select a logger provider that let you configure and turn these logging levels ON or OFF (preferrable at runtime if possible as well). Each level should able to automatically suppress all levels below it. And ofcourse you want a logger provider that can handle log message output to STDOUT and/or to FILE as destination as well.

Thursday, December 11, 2014

Getting started with Glassfish Server and Setting up SLF4J logging

Some notes I jot down while playing with GlassFish Server (3) for EE 6. You may get a working example here: https://github.com/saltnlight5/java-ee6-examples/tree/master/extra/glassfish-logging-example

= Gettin started with Glassfish server

== Start server

1. cd $GF/bin
2. asadmin start-domain domain1

== Stop server

1. cd $GF/bin
2. asadmin stop-domain domain1

== Server Ports

Admin Console Application is at http://localhost:4848
 * Default setup has no user and password restriction!

Web applications is http://localhost:8080

== To create a new domain with diferent ports

1. cd $GF/bin
2. asadmin create-domain --portbase 9000 domain2

* If you accept default then again no password for admin user. After this, your
  admin console app is at http://localhost:9048 while your application is at
  http://localhost:9080

= Glassfish Server Setup

== How to setup SLF4J

1. Copy slf4j-api and slf4j-jdk jars into $GF/lib/endorsed
2. Edit $GF/domains/domain1/config/logging.properties and add your own logging package level
 mypackage.level=FINEST

== How to enable JSTL tag for all web applications

1. Copy jstl-1.2.jar into $GF/domains/domain1/lib

== How to add MySQL Driver for all applications

1. Copy mysql-connector-java-5.1.30-bin.jar into $GF/domains/domain1/lib

Tuesday, December 2, 2014

How to secure a web application by the app server container

There are many benefits to allow a container in managing users, groups and authentication policies. You may configure your WAR application to take advantage of this by adding the following in the WEB-INF/web.xml file

   <security-constraint>
        <web-resource-collection>
            <web-resource-name>webuser</web-resource-name>
            <url-pattern>/*</url-pattern>

        </web-resource-collection>
        <auth-constraint>
            <role-name>webuser</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>default</realm-name>
    </login-config>
    <security-role>
        <role-name>webuser</role-name>
    </security-role>


Above will secure the entire application and allow only users with "webuser" role to access it. The name "webuser" can be any name you want.

Each app server will manage users differently. In the case of WLS, it lets you create "user" and "user group", and then you can map the defined role above to the group. To do this, add the following to the  WEB-INF/weblogic.xml file.

<weblogic-web-app
    xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app
        http://xmlns.oracle.com/weblogic/weblogic-web-app/1.2/weblogic-web-app.xsd">

    <security-role-assignment>
        <role-name>webuser</role-name>
        <principal-name>webusergroup</principal-name>
    </security-role-assignment>
</weblogic-web-app>


Here we tell WLS that we map the "webuser" role defined in web.xml to use the "webusergroup", a WLS user group.

To create  user or user group in WLS, you may use the WLS Admin console web application. Go to the Security Realm and select the default "myrealm", and then select User or Group tab. Go ahead and add a user with password under a new group named "webusergroup". After this you can deploy your app, and it would prompt you for user and password whenever you try to access its URL.

You can find out more security info at https://docs.oracle.com/cd/E23943_01/web.1111/e13711/thin_client.htm#SCPRG171.

Sunday, November 2, 2014

How to use SSH tunneling to get to your restricted servers

Have you ever been told that in your network serverX can only be reached by a serverY via SSH? Now you have access to serverY from your own PC with normal SSH access as well, but just not directly to serverX.

What can you do in situation like this if you need to access the restricted serverY? Well you can always ssh into serverY, then ssh again into serverX to check your work or log or whatever. But what happen if you have a database server or WebLogic Server instance running in serverX; and you want your local PC's fancy tools to access the serverX? (Eg: Accessing the WLS admin console, or using SqlDeveloper to connect to your DB etc). In this case, that's where ssh tunneling can help you, and here is how.

 1. Establish a connection to your serverY that you have access to from your PC. On top of that and at the same time, you will create a tunnel to serverX (your restricted server) by letting serverY redirect all the network traffic data back to your local PC on a specific port. Sounds scary, but it can be done with single command. For example this is how I can access the WLS Admin Console app that was running on the restricted server X. On your own PC, open a terminal and run the following:

bash> ssh -L 12345:serverX:7001 serverY

Above will prompt you to access serverY with your normal ssh credential that you have access to. Once logged in, you need to keep the terminal open. Now the tunnel is established and redirecting traffic from port 7001 (where the WLS admin console is running) on serverX to your own PC on port 12345 via the tunnel of server Y.

2. Open a browser on your own PC and type in address http://localhost:12345/console

Now you should able to access your restricted serverX's WLS admin console!

Same can be done with a database server such as MySQL. For example, you will run  ssh -L 12346:serverX:3306 serverY and then change your SqlDeveloper JDBC connection url string to the tunnel port jdbc:mysql://localhost:12346/mydb

This is a cool technique to get around a secured environment.

Wednesday, October 29, 2014

How to setup custom SSLSocketFactory's TrustManager per each URL connection

We can see from javadoc that javax.net.ssl.HttpsURLConnection provided a static method to override with setDefaultSSLSocketFory() method. This allow you to supply a custom javax.net.ssl.TrustManager that may verify your own  CA certs handshake and validation etc. But this will override the default for all "https" URLs per your JVM!

So how can we override just a single https URL? Looking at javax.net.ssl.HttpsURLConnection again we see instance method for setSSLSocketFactory(), but we can't instantiate HttpsURLConnection object directly! It took me some digging to realized that the java.net.URL is actually an factory class for its implementation! One can get an instance like this using new URL("https://localhost").openConnection()

To complete this article, I will provide a simple working example that demonstrate this.

package zemian;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class WGetText {
    public static void main(String[] args) throws Exception {
        String urlString = System.getProperty("url", "https://google.com");
        URL url = new URL(urlString);
        URLConnection urlConnection = url.openConnection();
        HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
        SSLSocketFactory sslSocketFactory = createTrustAllSslSocketFactory();
        httpsUrlConnection.setSSLSocketFactory(sslSocketFactory);
        try (InputStream inputStream = httpsUrlConnection.getInputStream()) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }

    private static SSLSocketFactory createTrustAllSslSocketFactory() throws Exception {
        TrustManager[] byPassTrustManagers = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }

            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }
        } };
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, byPassTrustManagers, new SecureRandom());
        return sslContext.getSocketFactory();
    }
}

Monday, October 27, 2014

How Servlet and JSP create sessions

In Servlet, you may get the Session object by "httpServletRequest.getSession(true)". The "true" flag will create the session if it doesn't already exist, else it gets the existing session.

Now if you want to check whether you have the session exists or not (without have to create one if doesn't exist), you need to pass in "false" and then check for "null".

Session session = httpServletRequest.getSession(false);
if (session == null) {
  // do something without creating session object.
}

Now comes the trick party. If you run above code and then dispatch the request to render a JSP page, you might quickly come to find out that the container will create a new Session object still! It turns out that by default JSP will create new Session object if you do not have one! To disable this, you need to set this explicitly on top of the JSP page:

 <% page session="false" %>

Only with this you will able to actually prevent creation of unnecessary Session object if you were to use JSP for output! Something to watch out for when debugging session based application.

Saturday, October 18, 2014

Poking around your REST application with a scriptable endpoint

I love the fact that JDK comes with a ScriptEngine. It's so flexible when you want to evaluate and troubleshoot your application that's already deployed in an server environment. Add this REST endpoint into a Java EE app, and it will give you instant access to internal states of the app.

package myrestapp;

import java.io.StringReader;
import java.util.logging.Logger;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;

/**
 * Give instant access to your internal application with dynamic scripting.
 *
 * <p>Example script:
 * <pre>
 * "sc" + servletContext + ", req=" + request;
 * </pre>
 *
 * <p>Example2
 * <pre>
 * names = servletContext.getAttributeNames();
 * while(names.hasMoreElements()) {
 *   name = names.nextElement();
 *   println(name);
 * }
 * </pre>
 *
 * <p>Example on how to import Java packages and classes.
 * <pre>
 * importPackage(Packages.java.text);
 * df = new SimpleDateFormat("MM/dd/yyyy");
 * dt = df.parse("01/01/2014");
 * </pre>
 */
@Path("script")
public class ScriptResource {
    private static final Logger logger = Logger.getLogger(ScriptResource.class.getName());
   
    @Context
    private ServletContext servletContext;
       
    @POST
    public String script(@Context HttpServletRequest request, String scriptText) throws Exception {
        String engineName = "JavaScript";
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName(engineName);
        logger.info("Running script text length=" + scriptText.length() + ", engine=" + engine);
        Object result = null;
        try (StringReader reader = new StringReader(scriptText)) {
            Bindings bindings = engine.createBindings();
            bindings.put("servletContext", servletContext);
            bindings.put("request", request);
            result = engine.eval(reader, bindings);
        }
        logger.info("Result " + result);
        return "RESULT=" + result;
    }
}


Notice that I gave couple JavaScript examples in the comment area already. You will have access to two binding variables that should give you full access to many internal components of your application. And here is a quick reference on scripting JDK 7.

Need an UI to test out this endpoint? How about give the "Advance Rest Client" Chrome Extension a try? (Thanks to my co-worker's Chris Griffith for showing me this cool extension. It's really handy tool to have!).

UPDATES (12/8/2014)
- If you are using FireFox, try the "RESTClient" add-ons.
- If you are using JDK7 Rhino JavaScript engine, here is a good ref for help: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scripting_Java
- If you're in a EE6 web application and doesn't have REST enabled yet, then simply add a class like this to your application. It should automatically configure an endpoint that you can access as "http://localhost/rest/script".

package myrestapp;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

/**
 * This is the main entry into REST Application that bootstrap the provider.
 */
@ApplicationPath("rest")
public class RestApplication extends Application {
}

Sunday, September 14, 2014

Book Review: Apache Camel Developer's Cookbook


I got a chance to review the "Apache CamelDeveloper's Cookbook" by Cranton and Korab. Overall I think this is a great book. System integration problems and solutions come in many forms, so getting started by reading on some proven solution recipes is definitely a good way to improve your skill. This book provides more than 100 examples on how to solve integration problems with Camel framework; from simple standalone Java application to testing, transaction, monitoring and even a chapter on web services. Each example comes with brief explanations and further reading references. My favorites are the side notes sprinkled throughout each recipe. Clearly these great tips can only have came from well experienced Camel developers who has spent time on the field.

As many cookbook style, the book can only go to certain length with each example on explanation and teaching, but readers may dig much deeper by using the sample code provided by this book. In fact I think it's really cool that it's available through GitHub as well. Check it out at http://github.com/CamelCookbook/camel-cookbook-examples. (although I think there is a typo in the book for this URL on the copy I have! ^_^) The sample code are complete, clean and easy to follow for each recipe example. The source code is in Maven based project, so you will get all the dependencies needed by just running the "install" phase. Open by any major IDE and you will start reading and compiling immediately. Because it's using Maven, you can also download the Camel dependencies with Source, and you can jump right into the framework code itself to analyze what's behind this cool project.

Because it's a cookbook, it does not go into too deep about Camel internal. But through the examples, many of the core concepts of Camel has been touched; and it serves as a great example and can be used as handy reference book. Because it covers the Camel concept briefly, it expected you to know little bit of the integration knowledge and background though. Things like Enterprise Integration Patterns and transport technologies used such as File, FTP, SEDA, JMS etc. The book also comes with many Spring based XML configuration examples, and it expects you to know some basic knowledge of bean configuration. But the xml configuration of Camel routes itself are very self explanatory, so readers should able to follow along easily.

If you work with Camel project, or have to start an integration project, I would recommend you to check out this book.

Friday, September 5, 2014

Django with Python 3 and MySQL database


I read many folks are having problems using MySQL db driver with Python 3, especially when setting up a Django app. The default Django 1.6.5 is only supporting the MySQLdb driver and that only works with Python 2.

I have been using mysql-connector-python package with Python 3 and it has built-in django support as well. I had ran into trouble like this http://bugs.mysql.com/bug.php?id=73232, but it is fixed now with the latest mysql-connector-python 1.2.3 release. The mysql-connector-python also works with Python 2.7 as well, and it's a pure python library without native code, which makes the install much easier.

When installing mysql-connector-python, ensure you allow external hosted files like this

pip install --allow-all-external mysql-connector-python

If you are behind a firewall, use the proxy option

pip install --proxy my_proxy_server --allow-all-external mysql-connector-python

With these, now you can set your Django settings.py file with MySQL engine

DATABASES = {
    'default': {
        'ENGINE': 'mysql.connector.django',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
    }
}


PS: My initial testing with Django 1.7 also works pretty well with mysql-connector-python. How sweet!

UPDATE 07/15/2015:
The django 1.8 documentation now recommends using 'mysqlclient' package if you want to use Python3 with MySQL backend. It should be a drop in replacement for 'MySQLdb'.

Thursday, August 21, 2014

Deploying applications or libraries to WebLogic Server using command line

Here is how you can automate deployment for WebLogic server using command line.

First source the env settings from the server:
$ source $ML_HOME/server/bin/setWLSEnv.sh

Deploy Library:
$ java weblogic.Deployer -nostage -deploy -library \
-adminurl localhost:7001 \
-username weblogic -password my_secret \
-targets myserver \
my_shared_lib.war


Deploy Application:
$ java weblogic.Deployer -nostage -deploy \
-adminurl localhost:7001 \
-username weblogic -password my_secret \
-targets myserver \
-name myapp.war myapp.war


For development, you likely want to use the "-nostage" meaning to deploy the app or library directly from the file system. This means any changes to that file location and a reload from WLS will take effect immediately.

For undeploy the command line options are same for library or app but with matching name.
$ java weblogic.Deployer -undeploy \
-adminurl localhost:7001 \
-username weblogic -password my_secret \
-targets myserver \
-name myapp_or_lib.war

Wednesday, August 20, 2014

WebLogic shared library deployment

When deploying a large WAR file application, it would be more easier to manage if we can separate the dependency jars away from the rest of the Web content; or at least those third party jars that do not update often. In this case, we usually call the jars content a "Shared Library" and the Web content the "Skinny WAR".

With WebLogic Server, you can easily deploy such two artifacts. Just seperate and package your WAR application into two. The share library would be simply another WAR with only the WEB-INF/lib content in it, while the Skinny war will be the rest of your application without the jar depependencies. On the shared lib WAR file, ensure you have an META-INF/MANIFEST.MF that specify the name and version like the following:

Implementation-Title: my_shared_lib
Implementation-Version: 1.0

Specification-Title: my_shared_lib
Specification-Version: 1.0

Extension-Name: my_shared_lib-1.0

Now your Skinny WAR would need to add an WEB-INF/weblogic.xml extension file to reference the library like this:

<weblogic-web-app>
    <library-ref>
        <library-name>my_shared_lib</library-name>
        <specification-version>1.0</specification-version>
        <implementation-version>1.0</implementation-version>
        <exact-match>true</exact-match>
    </library-ref>
</weblogic-web-app>



With these two packaged, now turn to your WLS admin console, you will find "Deployments" menu link on left, and on right, you click "Install" button. The next screen will prompt you to choose which type of deployment to install: "Library" (Shared Lib War) or "Application" (Skinny War). Re-run this twice, each with your two seperated WAR files you just built.



The WLS will combine the two when running your WAR application. This comes handy if you are to deploy multiple instances of your Skinny war application, but now you only need one shared lib.

NOTE: Ensure you select at least one, and the same Target servers where you deploy the Library and Application. Else your application will not be deployed and run.