Tomcat - Jersey - Swagger.Json not getting generated - swagger

I want to generate Swagger API documentation for ReST service developed using Jersey.
I have gone through Swagger API documentation and deployed as per the documentation.
But i am getting the following output when i access the URL (http://localhost:8080/MySwagger/swagger.json).
{
"swagger" : "2.0",
"info" :
{
"version" : "1.0.2",
"title" : "UUK"
},
"host" : "localhost:8080",
"basePath" : "/MySwagger",
"schemes" : [ "http" ]
}
The above one deosn't contain API that we have developed
I have tried the various options mentioned in the documentation but the result is same.
I have deployed the application in tomcat 7.0.68.
My Java version is 7
I have used maven to download the dependency Jars and manually copy the Jars to /WEB-INF/lib directory
Maven Script
mvn dependency:get -DgroupId=io.swagger -DartifactId=swagger-jersey2-jaxrs -Dversion=1.5.7
My web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
io.swagger.jaxrs.listing.ApiListingResource,
io.swagger.jaxrs.listing.SwaggerSerializers,
test.EventReceiver
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>SwaggerBootstrap</servlet-name>
<servlet-class>test.bootstrap.SwaggerApplication</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
My Custom Servlet Class
import io.swagger.jaxrs.config.BeanConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class SwaggerApplication extends HttpServlet
{
private static final long serialVersionUID = 1L;
#Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.2");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:8080");
beanConfig.setBasePath(config.getServletContext().getContextPath());
beanConfig.setTitle("UUK");
beanConfig.setResourcePackage("io.swagger.resources");
beanConfig.setScan(true);
beanConfig.setPrettyPrint(true);
}
}
My API Class
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path(value="/v1/")
#Api(value="inboundtransaction" ,description = "Receive Inbound transaction", position = 1, protocols = "http")
public class EventReceiver
{
#POST
#Path(value="/inboundtransaction")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes({ MediaType.APPLICATION_JSON })
#ApiOperation(value="Receiving Inbound transaction from X", notes = "Must Contain ID",httpMethod = "POST",response=String.class)
#ApiResponses(value =
{
#ApiResponse(code = 201, message = "Received request and process later", response=String.class),
#ApiResponse(code = 400, message = "Server Busy. Please re-send later", response=String.class)
}
)
public String inboundtransaction(
#ApiParam(value = "Pass mandatory values to create EdiAddress",required = true) String data
)
{
return "Success";
}
}
Please let me know what is the issue in the current setup.
Regards
Udhaya

you need to include your package , it is a string with comma sepearated packages.
beanConfig.setResourcePackage("io.swagger.resources, , ");

Related

Swagger interacting with Resteasy not listing content api

We're trying to have Swagger interact with our resteasy app so that the api will list out on the swagger page. We're using annotations in the Java resources and not a yaml/json file. We want the content to be 'dynamic' or come directly from the resource pages.
When we bring up the swagger page, we get the message 'fetching resource list: //10.155.63.136/nodeMgmt'. When I render the page using firebug, I see that the page itself (index.html) is being 'fed in' to the content.
I think I'm pretty close but am missing one or two small things. I used the example as a guide to what I did: https://github.com/mrj365/RestEasy-3.09-Spring-3.2.5-Swagger2.0
Again, the issue is that the content from the resteasy api is not being fed into the swagger ui. The url in my case is https://10.155.63.92/nodeMgmt/index.html
We're using JBoss 6.4, NO Spring, Resteasy 3.0.7, Swagger jaxrs 1.5.9.
Any help is really appreciated.
Index.html
<script type="text/javascript">
$(function () {
window.swaggerUi = new SwaggerUi({
url: "/nodeMgmt",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
onComplete: function(swaggerApi, swaggerUi){
log("Loaded SwaggerUI");
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "none"
});
web.xml
<!-- Auto scan REST service -->
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/nodes</param-value>
</context-param>
<filter>
<filter-name>ApiOriginFilter</filter-name>
<filter-class>com.sonus.unity.sonusbaserestservice.utils.ApiOriginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ApiOriginFilter</filter-name>
<url-pattern>/index.html</url-pattern>
</filter-mapping>
<!-- if you are using Spring, Seam or EJB as your component model, remove the ResourceMethodSecurityInterceptor -->
<context-param>
<param-name>resteasy.resource.method-interceptors</param-name>
<param-value>
org.jboss.resteasy.core.ResourceMethodSecurityInterceptor
</param-value>
</context-param>
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.sonus.ems.nodemgmt.web.NodeMgmtApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/nodes/*</url-pattern>
</servlet-mapping>
application.java
#ApplicationPath("")
public class NodeMgmtApplication extends SonusBaseRestApplication {
private static final Logger log = Logger.getLogger(NodeMgmtApplication.class);
/**
* Constructor
*/
public NodeMgmtApplication() {
super();
try {
//TODO Swagger
// Used for defining swagger
BeanConfig beanConfig = new BeanConfig();
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:8080");
beanConfig.setVersion("1.5.9");
beanConfig.setBasePath("/nodeMgmt");
beanConfig.setResourcePackage("com.sonus.ems.nodemgmt.web");
//beanConfig.setPrettyPrint(true);
beanConfig.setScan(true);
addService(new NodeMgmtAuthorizationFilter());
addService(new NodeMgmtRestService());
// Swagger
addService(new ApiListingResource());
addService(new SwaggerSerializers());
} catch (Exception e) {
log.error("NodeAdminApplication: Could not instantiate singletons " + e);
}
}
resource.java
#Path("/")
#Api(value = "/", description = "Node operations", produces = MediaType.APPLICATION_JSON, consumes = MediaType.APPLICATION_JSON)
public class NodeMgmtRestService {
private static final Logger log = Logger.getLogger(NodeMgmtRestService.class);
private static final String NODES = "nodes";
private static final String ID = "id";
NodeMgmtServiceProvider nodeMgmtServiceProvider = new NodeMgmtServiceProvider();
Service nodeMgmtService;
#GET
#Path("/{version}")
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Get all nodes", notes = "Returns a list of node", response = String.class, responseContainer="List")
public Response getNodes(
#ApiParam(value = "Version of api (1.0)", required = true) #PathParam("version") String version,
#ApiParam(value = "Filter by ", required = true) #QueryParam("filterParam") String filterParam,
#ApiParam(value = "Filter value ", required = true) #QueryParam("filterValue") String filterValue) {
List<Node> nodeList = new ArrayList<Node>();
List<Object> nodeJsonList = new ArrayList<Object>();
Map<String, List<Object>> nodeJsonMap = new HashMap<String, List<Object>>();
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> responseId = new HashMap<String, String>();
JsonNodeDao jsonNodeDao = new JsonNodeDao();
Swagger API key
public class ApiOriginFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
// Add access to the header
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS");
res.addHeader("Access-Control-Allow-Headers", "Content-Type, api_key, Authorization");
//res.addHeader("Access-Control-Allow-Headers", "Origin, X-Atmosphere-tracking-id, X-Atmosphere-Framework, X-Cache-Date, Content-Type, api_key, Authorization, X-Atmosphere-Transport, x-requested-with, Total-Count, Total-Pages, Error-Message, *");
//res.addHeader("Access-Control-Request-Headers", "Origin, X-Atmosphere-tracking-id, X-Atmosphere-Framework, X-Cache-Date, Content-Type, api_key, Authorization, X-Atmosphere-Transport, x-requested-with, Total-Count, Total-Pages, Error-Message, *");
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}

embedded a ldap server with spring-security using java (instead of xml)

I need to embeed a LDAP server with spring for testing purposes. The following code works:
src/main/webapp/WEB-INF/web.xml
[...]
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
[...]
com.example.config.servlet.TestLDAPServerConfiguration
</param-value>
</context-param>
[...]
src/main/java/com/example/config/servlet/TestLDAPServerConfiguration.java
package com.example.config.servlet;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
#Configuration
#ImportResource({"/WEB-INF/test-ldap-server.xml"})
public class TestLDAPServerConfiguration {
}
src/main/webapp/WEB-INF/test-ldap-server.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:s="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<s:ldap-server ldif="classpath:users.ldif" root="dc=nestle,dc=com" port="33389"/>
</beans>
I do need to use AnnotationConfigWebApplicationContext instead of XmlWebApplicationContext. However, here I am using a class TestLDAPServerConfiguration which just imports a test-ldap-server.xml, and this test-ldap-server.xml just declares the ldap-server.
I'd like to remove the test-ldap-server.xml file. How can I do the equivalent of s:ldap-server with java code inside the TestLDAPServerConfiguration class? And where is this documented?
Try something like this;
protected static ApacheDSContainer server;
#BeforeClass
public static void startServer() throws Exception {
server = new ApacheDSContainer( "dc=yourdomain,dc=com", "classpath:test-server.ldif" );
server.setPort( 53389 );
server.afterPropertiesSet();
server.start();
}
#AfterClass
public static void stopServer() throws Exception {
if( server != null ) {
server.stop();
}
}
You can get guides from the spring-security-ldap source code.

Trouble creating a simple singleton class in Jersey 2 using built-in Jersey dependency injection

I am having trouble getting a very basic implementation of a singleton class off the ground with Jersey 2 (2.7) and only Jersey's built-in HK2 dependency injection. I am running this on Tomcat.
My goal is to create a singleton instance of a support class that will be used by various web service methods. I don't have a strong preference between constructor injection, method injection, and annotating a class member (as I do below).
Here is my to-be-singleton class:
package singletest;
import javax.inject.Singleton;
#Singleton
public class JustOne {
private int secretNumber = 0;
public void hitMe(int input) {
secretNumber += input;
}
#Override
public String toString() {
return String.format("{ \"secretNumber\": %s }", secretNumber);
}
}
Here is my application class:
package singletest;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
#ApplicationPath("/*")
public class MainApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(TestResource.class);
return classes;
}
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
singletons.add(new JustOneProvider());
return singletons;
}
}
Here is my Provider/ContextResolver class:
package singletest;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class JustOneProvider implements ContextResolver<JustOne> {
private static JustOne justOne = new JustOne();
#Override
public JustOne getContext(Class<?> type) {
return justOne;
}
}
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 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_2_5.xsd">
<filter>
<filter-name>singletest.MainApplication</filter-name>
<filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>singletest.MainApplication</param-value>
</init-param>
<!-- pass to next filter if Jersey/App returns 404 -->
<init-param>
<param-name>jersey.config.servlet.filter.forwardOn404</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>singletest.MainApplication</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
The resource where I intend to inject the singleton instance of my JustOne class:
package singletest;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/test")
public class TestResource {
#Inject
private JustOne justOne;
#GET
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
#Path("/func1/{input}")
public Response func1(#PathParam("input") int input) {
justOne.hitMe(input);
String responseData = justOne.toString();
return Response.ok(responseData).build();
}
}
This warning is raised when the war is deployed/initialized in Jersey:
Apr 28, 2014 11:48:25 AM org.glassfish.jersey.server.ApplicationHandler initialize
INFO: Initiating Jersey application, version Jersey: 2.7 ${buildNumber}...
Apr 28, 2014 11:48:25 AM org.glassfish.jersey.internal.inject.Providers checkProviderRuntime
WARNING: A provider java.lang.Class registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider java.lang.Class will be ignored.
And the error I get when I call this web service:
type Exception report
message A MultiException has 3 exceptions. They are:
description The server encountered an internal error that prevented it from fulfilling this request.
exception
javax.servlet.ServletException: A MultiException has 3 exceptions. They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=JustOne,parent=TestResource,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,58952407)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of singletest.TestResource errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on singletest.TestResource
org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:333)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:372)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:335)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:218)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
So, I get it: hk2 isn't getting enough information to create/bind a JustOne instance and/or find/use my JustOneProvider. I'm sure I'm missing something basic. Any guidance would be appreciated.
bind(JustOne.class).to(JustOne.class).in(Singleton.class);
is the most important part. Without "in(Singleton.class)" multiple instances of JustOne will be created.
Okay, after spending a very healthy chunk of time on this, I got it working.
I used an HK2 AbstractBinder and JAX-RS Feature (javax.ws.rs.core.Feature).
It turned out that no Provider/ContextResolver was required.
Application:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
#ApplicationPath("/*")
public class MainApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(TestResource.class);
classes.add(JustOneFeature.class);
return classes;
}
}
To-be-singleton class:
public class JustOne {
private int secretNumber = 0;
public int getSecretNumber() {
return secretNumber;
}
public void bumpSecretNumber() {
secretNumber += 1;
}
}
Binder:
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class JustOneBinder extends AbstractBinder {
#Override
protected void configure() {
bind(new JustOne()).to(JustOne.class);
}
}
Feature:
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
public class JustOneFeature implements Feature {
#Override
public boolean configure(final FeatureContext context) {
context.register(new JustOneBinder());
return true;
}
}
Resource that gets singleton injected:
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/test")
public class TestResource {
#Inject
private JustOne justOne;
#GET
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
#Path("/func1/{input}")
public Response func1(#PathParam("input") int input) {
justOne.bumpSecretNumber();
String responseData = String.format("{ \"result\": %s }", input + justOne.getSecretNumber());
return Response.ok(responseData).build();
}
}

No Details in Log: The module has not been deployed. See the server log for details

I've created a very simple application (Just an index file), and a Java Servlet filter.
I'm seeing the following error in the logs on deploy from Netbeans:
Deployment is in progress...
deploy?config=file%3A%2FC%3A%2FUsers%2Fuser%2FAppData%2FLocal%2FTemp%2Fcontext490289541373061276.xml&path=/EXD
http://localhost:8080/manager/deploy?config=file%3A%2FC%3A%2FUsers%2Fuser%2FAppData%2FLocal%2FTemp%2Fcontext490289541373061276.xml&path=/EXD
C:\Users\user\Documents\NetBeansProjects\EXDLogger\nbproject\build-impl.xml:1070: The module has not been deployed.
See the server log for details.
BUILD FAILED (total time: 57 seconds)
There are not any errors in any of the other logs.
Any idea what is going on?
web.xml content:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 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_2_5.xsd">
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>EXDLogger</filter-name>
<filter-class>com.cn.filters.EXDLogger</filter-class>
<init-param>
<param-name>paramExclude</param-name>
<!-- Parameters which should be excluded from the hash in determining
if a request is unique. Separate multiple values with a ; -->
<param-value>PARAM_1;PARAM_2</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EXDLogger</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Class being called:
package com.cn.filters;
import java.io.IOException;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class EXDLogger implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//Get the IP address of client machine.
String ipAddress = request.getRemoteAddr();
//Log the IP address and current timestamp.
System.out.println("IP "+ipAddress + ", Time "
+ new Date().toString());
chain.doFilter(req, res);
}
public void init(FilterConfig config) throws ServletException {
//Get init parameter
String testParam = config.getInitParameter("paramExclude");
//Print the init parameter
System.out.println("Test Param: " + testParam);
}
public void destroy() {
//add code to release any resource
}
}
I hacked an answer. I'm sure there are better ones. I updated the relevant section of my build-impl.xml as follows:
<target depends="init,-init-cos,compile,compile-jsps,-do-compile-single-jsp,-pre-dist,-do-tmp-dist-with-manifest,-do-tmp-dist-without-manifest,-pre-run-deploy,-pre-nbmodule-run-deploy,-run-deploy-nb,-init-deploy-ant,-deploy-ant,-run-deploy-am,-post-nbmodule-run-deploy,-post-run-deploy,-do-update-breakpoints" name="run-deploy"/>
<target if="netbeans.home" name="-run-deploy-nb">
<!-- <delete dir="${deployTarget}"/> -->
<copy toDir="${deployTarget}" overwrite="true">
<fileset dir="${build.web.dir}"></fileset>
</copy>
<copy toDir="${deployTarget}/WEB-INF/classes" overwrite="true">
<fileset dir="${build.classes.dir}"></fileset>
</copy>
<!-- <nbdeploy clientUrlPart="${client.urlPart}" debugmode="true" forceRedeploy="${forceRedeploy}"/> -->
</target>
I then added to my project.properties:
deployTarget=c:/xampp/tomcat/webapps/EXD
client.url=http://localhost:8080/EXD/

Apache-Tiles3 and Freemarker where freemarker result is processed from String, renderer output is too early

I'm integrating struts2, tiles3 and Freemarker (templates from a DB). The integration is mostly done, but the freemarker renderer is rendering to the result immediately instead of inserting its content into the template at the expected location.
Any advice on how to correct this?
Expected Output:
<!DOCTYPE html>
<html>
<body>
<h1>Template</h1>
<strong>A bold statement!</strong><div>a message from the Action...</div><div> I came from: the_location</div><div>a message from the Action...</div>
</body>
</html>
Actual Output (Not formatted for readability because this is a template issue and not an html issue so accuracy of output counts):
<strong>A bold statement!</strong><div>a message from the Action...</div><div> I came from: the_location</div>
<!DOCTYPE html>
<html>
<body>
<h1>Template</h1>
</body>
</html>
tiles-defs.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="test_def2" template="/WEB-INF/template/template.jsp">
<put-attribute name="body" value="the_location" type="db_freemarker"/>
</definition>
</tiles-definitions>
/WEB-INF/template/template.jsp
<%#taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%#taglib prefix="s" uri="/struts-tags"%>
<%#page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<body>
<h1>Template</h1>
<tiles:insertAttribute name="body"/>
</body>
</html>
Created and registered a DbFreemarkerTilesRenderer with key "db_freemarker", this is the implementation of DbFreemarkerTilesRenderer:
package com.quaternion.struts2.result.freemarker;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tiles.request.Request;
import org.apache.tiles.request.freemarker.render.FreemarkerRenderer;
import org.apache.tiles.request.render.CannotRenderException;
import org.apache.tiles.request.servlet.ServletRequest;
public class DbFreemarkerTilesRenderer extends FreemarkerRenderer{
public DbFreemarkerTilesRenderer(){
super(null); //Expects a AttributeValueFreemarkerServlet
}
/** {#inheritDoc} */
#Override
public void render(String location, Request request) throws IOException {
if (location == null) {
throw new CannotRenderException("Cannot dispatch a null path");
}
ServletRequest servletRequest = org.apache.tiles.request.servlet.ServletUtil.getServletRequest(request);
HttpServletRequest httpRequest = servletRequest.getRequest();
HttpServletResponse httpResponse = servletRequest.getResponse();
DataBaseTemplateLoader dataBaseTemplateLoader = new DataBaseTemplateLoader();
try {
dataBaseTemplateLoader.render(location, httpRequest, httpResponse);
} catch (Exception ex) {
Logger.getLogger(DbFreemarkerTilesRenderer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
The "DataBaseTemplateLoader" actually loads a freemarker template from a hard coded string, but it will load from a DB once this issue is resolved (see the render method the rest of the class is included for context, Struts2 developers will recognize it as a Result type, the idea being the Freemarker DB functionality can be provided directly to Struts2 as a Result):
package com.quaternion.struts2.result.freemarker;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.ServletDispatcherResult;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.util.ContextUtil;
public class DataBaseTemplateLoader extends ServletDispatcherResult {
private static final Logger log = Logger.getLogger(DataBaseTemplateLoader.class.getName());
private String defaultEncoding = "ISO-8859-1"; //TODO: Hardcoded?
private FreemarkerManager freemarkerManager;
private Configuration configuration;
//Inject works if this is a Struts2 component (Result) however
//when using this from Tiles, the following is not injected...
#Inject
public void setFreemarkerManager(FreemarkerManager mgr) {
this.freemarkerManager = mgr;
}
public DataBaseTemplateLoader() {
super();
}
public DataBaseTemplateLoader(String location) {
super(location); //TODO: look up template based on "location" string
}
protected Configuration getConfiguration() throws TemplateException {
if (freemarkerManager == null){
//Force injection
freemarkerManager = ActionContext.getContext().getContainer().getInstance(FreemarkerManager.class);
}
return freemarkerManager.getConfiguration(ServletActionContext.getServletContext());
}
#Override
public void doExecute(String location, ActionInvocation invocation) throws Exception {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
render(location, request, response);
}
public void render(String location, HttpServletRequest request, HttpServletResponse response) throws Exception {
//location is String passed from Struts2 action which will formated
//in such a way to apply conventions though to view
//Format: namespace#action#method
//setLocation(location);
StringTemplateLoader stringLoader = new StringTemplateLoader();
//BOTH "name" and "source" will be retrieved from the DB
String name = "firstTemplate";
String source = "<strong>A bold statement!</strong><div>${action.message}</div><div> I came from: " + location + "</div>";
stringLoader.putTemplate(name, source);
// It's possible to add more than one template (they might include each other)
// String secondTemplate = "<#include \"greetTemplate\"><#greet/> World!";
// stringLoader.putTemplate("greetTemplate", secondTemplate);
freemarker.template.Configuration cfg = getConfiguration();
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate(name);
Map map = ContextUtil.getStandardContext(ActionContext.getContext().getValueStack(), request, response);
response.setContentType("text/html;charset=" + defaultEncoding);
template.process(map, response.getWriter());
}
}
The Struts2 Action (not needed, just shows where "a message from the Action..." comes from:
package com.quaternion.tilesdb.action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
/**
* Tests Tiles with Freemarker integration, where the freemarker template
* is parsed from a String, this is to allow later integration with freemarker
* database templates
*/
#ParentPackage("quaternion-results")
#Result(type="tiles", location="test_def2")
public class TilesFtlTest2 extends ActionSupport{
private String message = "a message from the Action...";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
I just want to close this question off, the answer is I was missing tiles configuration from web.xml (the Freemarker servlet configuration).
Here is the web.xml which allows the above to work:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 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_2_5.xsd">
<listener>
<listener-class>com.quaternion.struts2.result.freemarker.MoreCompleteAutoloadTilesListener</listener-class>
<!--<listener-class>org.apache.tiles.extras.complete.CompleteAutoloadTilesListener</listener-class>-->
</listener>
<!-- Following is required to enable freemarker(ftl) support-->
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>org.apache.tiles.request.freemarker.servlet.SharedVariableLoaderFreemarkerServlet</servlet-class>
<!-- FreemarkerServlet settings: -->
<init-param>
<param-name>TemplatePath</param-name>
<param-value>/WEB-INF/template/</param-value>
</init-param>
<init-param>
<param-name>NoCache</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>ContentType</param-name>
<param-value>text/html</param-value>
</init-param>
<!-- FreeMarker settings: -->
<init-param>
<param-name>template_update_delay</param-name>
<param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
</init-param>
<init-param>
<param-name>default_encoding</param-name>
<param-value>ISO-8859-1</param-value>
</init-param>
<init-param>
<param-name>number_format</param-name>
<param-value>0.##########</param-value>
</init-param>
<init-param>
<param-name>org.apache.tiles.request.freemarker.CUSTOM_SHARED_VARIABLE_FACTORIES</param-name>
<param-value>tiles,org.apache.tiles.freemarker.TilesSharedVariableFactory</param-value>
</init-param>
<load-on-startup>5</load-on-startup>
</servlet>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>/index.action</welcome-file>
</welcome-file-list>
</web-app>

Resources