Sunday, February 22, 2009

Getting Struts 2.1 to work in Weblogic 10.3

Over the last couple of days, I've been looking into the problems of running Struts2 in Weblogic 10.3. Once you wade through the errors however, the resolution is not as complicated. So, the main reason why Struts 2.1 does not work in Weblogic 10.3 (and could be earlier versions) is mainly because of the way Weblogic handles the WEB-INF classes and lib directories. The classes directory is zipped up into a jar called _wl_cls_gen.jar. Struts 2.1 specifically is looking for actions either in the classes directory or in a jar with a "META-INF/Manifest". Unfortunately, the jar created does not have the manifest and Struts2 does not know that it needs to look in a jar for the struts actions. Also, every jar within a WAR file is available as a resource via the zip file protocol, instead of a more standard jar file protocol (a really quirky implementation from BEA on this....:(, shame on them!!!).

So then how does one go about fixing these issues? Here are the steps:

  • You will need to add the META-INF/Manifest file into the classes directory of your WAR. This will guarantee that the _wl_cls_gen.jar is a valid jar that Struts2 will recognize.

  • You will then need to add a struts.xml to your project with the following information:
    <constant name="struts.convention.action.includeJars" value=".*_wl_cls_gen.*"/>
    Note here that with Struts 2.1, you will be using the struts2-convention plugin, and this is an override for one of the constants that the convention plugin uses to look for actions.

  • So far, these steps were pretty easy. Now to a little bit of coding work. You will need to change a small piece of code in the convention plugin. There are 2 pieces. One piece is the change in the plugin's struts-plugin.xml. In talking with Musachy Barroso, a lead developer on Struts, this change has already been committed to the trunk. This change is to allow the convention plugin to accept the zip protocol. The change in the struts-plugin.xml is:
    <constant name="struts.convention.action.fileProtocols" value="jar,zip" />

    The next is the following piece of code in the org.apache.struts2.convention.PackageBasedActionConfigBuilder class:


    private Set fileProtocols;

    @Inject("struts.convention.action.fileProtocols")
    public void setFileProtocols(String fileProtocols) {
    if (!StringTools.isTrimmedEmpty(fileProtocols)) {
    this.fileProtocols = TextParseUtil.commaDelimitedStringToSet(fileProtocols);
    }
    }

    private UrlSet buildUrlSet() throws IOException {
    UrlSet urlSet = new UrlSet(getClassLoader());

    urlSet = urlSet.exclude(ClassLoader.getSystemClassLoader().getParent());
    urlSet = urlSet.excludeJavaExtDirs();
    urlSet = urlSet.excludeJavaEndorsedDirs();
    urlSet = urlSet.excludeJavaHome();
    urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
    urlSet = urlSet.exclude(".*/JavaVM.framework/.*");

    if (includeJars == null) {
    urlSet = urlSet.exclude(".*?jar(!/)?");
    } else {
    //jar urls regexes were specified
    List rawIncludedUrls = urlSet.getUrls();
    Set includeUrls = new HashSet();
    boolean[] patternUsed = new boolean[includeJars.length];

    //- Changed section begins
    for (URL url : rawIncludedUrls) {
    if (fileProtocols.contains(url.getProtocol())) {
    //it is a jar file, make sure it macthes at least a url regex
    for (int i = 0; i < includeJars.length; i++) {
    String includeJar = includeJars[i];
    String extUrlForm = url.toExternalForm();
    if (Pattern.matches(includeJar, extUrlForm)) {
    // If the protocol is zip, convert to jar protocol
    if ( extUrlForm.indexOf("zip:")!=-1 ) {
    String newUrl = "jar:file:/" +
    extUrlForm.substring(4);
    url = new URL(newUrl);
    }
    includeUrls.add(url);
    patternUsed[i] = true;
    break;
    }
    }
    } else {
    //it is not a jar
    includeUrls.add(url);
    }
    }
    //- Changed section ends
    if (LOG.isWarnEnabled()) {
    for (int i = 0; i < patternUsed.length; i++) {
    if (!patternUsed[i]) {
    LOG.warn("The includeJars pattern [#0] did not match any jars in the classpath", includeJars[i]);
    }
    }
    }
    return new UrlSet(includeUrls);
    }

    return urlSet;
    }

    The piece of code that hasn't been committed and in talking with Musachy Barroso is felt temporary is the conversion of zip protocol to jar in the buildUrlSet method. This hasn't been committed to the trunk and most likely will go in as a broader code change that will affect the XWork library that Struts2 uses.


What I've laid out are fairly simple changes - compiling the struts2 convention plugin should be a cakewalk (maven based). I will keep this site updated as I hear more from Musachy if you are willing to wait it out before trying Struts2 in Weblogic. Let me know if you have problems getting things to work....

- Amit

UPDATE:
The above changes are not required with Struts 2.2. Just put the following in the struts.properties file of the project.
#struts convention property modifications for Weblogic
struts.convention.action.includeJars=.*?/your-web-app-name.*?jar(!/)?
struts.convention.action.fileProtocols=jar,zip

15 comments:

emil said...

Hello Amit,

thanks for the proposed solution. I've been having problems using the new convention plugin on Weblogic 10.3 too.

I tried implementing the changes you suggested, but unfortunately I still can't get it to work. After applying the fix, my application's actions were properly listed in the config-browser, but when I tried to retrieve a page the properties of my action were not displayed in the view. I used a debugger to make sure that the action was actually initialised and the execute method got called just as expected. However, the get method of the property I wanted to display was never called ... Setting values from a form seems to work fine, though.

Any idea of what could be wrong?

Kind regards,
Emil

Amit said...

Blogger Amit said...

Quick simple example to try which works for me:

@Namespace("/test")
@Results( { @Result(name = "success", location = "/index.jsp") } )
public class HelloWorldAction extends ActionSupport
{
public String execute() throws Exception
{
return ActionSupport.SUCCESS;
}

/**
* @return the greetings
*/
public String getGreetings()
{
return "Hello Amit";
}
}

And then reference the property in a jsp as follows:
<%@ taglib prefix="s" uri="/struts-tags">
<html<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Hello</title>
</head>
<body>
<p>Hey :<s:property value="greetings"/></p>
</body>
</html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Hello</title>
</head>
<body>
<p>Hey :<s:property value="greetings"/></p>
</body>
</html>

You probably have something similar I presume. If not, try this out - this works for me.

emil said...

Actually, my template did look slightly different, instead of using <s:property value="..." /> I was using EL style syntax, ${ ... }. This worked fine while running on Tomcat, but when I deployed my application on WebLogic the properties weren't displayed. I'm not sure which of the two is doing the right thing :-)

I've changed my code to use the Struts tag and now it works perfectly. Thanks again!

/e.

ngandhi said...

Hello,

Could you please tell me what do I write in the META-INF/Manifest.mf file?

Thanks
Nilima

Chris said...

Actually Amit, we did not need the MANIFEST file, we just need a file within META-INF. The file does not need to have anything in it.

Chris

Amit said...

Chris is right, however, I purposely did not suggest that because adding any file in the META-INF doesn't hold a meaning to the application classes. Creating a META-INF with a MANIFEST is in line with the JAR packaging standard.

I would recommend from a jar perspective, that you create a proper manifest. The manifest could be as simple and have just this in it -

Manifest-Version: 1.0
Built-By: YourName
Build-Jdk: 1.6.0_07

- Amit

John Liptak said...

I also added a property to inject the "jar:file:/" so that I could make it "jar:file:" on Linux.

Otherwise you get jar:file:// and xwork tries to load it as a URL.

Ryan said...

Brilliant, thank you! This saved us hours of frustration.

Stephen said...

Fantastic. This solved the issue. Why Weblogic decides to do things like this is beyond me.

vick said...

Hello Amit,

thanks before,
i tried your solution, but im still getting error like this
Error loading configuration file struts.xml - [unknown location]

im using weblogic 10.3

do you know why ?

Vicky

vick said...
This comment has been removed by the author.
Amit said...

Vicky

The code I've put down isn't required anymore. The updates are already now part of Struts. Just enhance the struts.properties file with the following:

#struts convention property modifications for Weblogic
struts.convention.action.includeJars=.*?/yourwebapp.*?jar(!/)?
struts.convention.action.fileProtocols=jar,zip

Ɓukasz Lenart said...

I added your solution to official Struts 2 Docs, thanks for support!
https://cwiki.apache.org/WW/weblogic.html

javaman said...

Hi,
What is 'yourwebapp' supposed to be? I used the same statements but Weblogic is showing 'the includeJars pattern yourwebapp.* did not match any jars in the classpath.

Amit said...

Been a while, but if you generate a jar to package the WEB-INF/classes into a jar while building the WAR file, then the name of that jar is what you’d give in this pattern.

In maven, your WAR plugin can be tweaked to create the jar
<archiveClasses>true</archiveClasses>

Hope that helps.