Issue with creating Custom Tag Libraries in CQ5.5 - osgi

I am creating a custom tag library and want to use it in one of my components. I have created a bundle which includes tag hello class which is extending TagSupport class and i created tags.tld file under my resource folder
In my pom.xml, I have used resource tag to include my .tld file in the generated jar file.
Here is my java class and tld file
TAG CLASS:-
package com.cb;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* Simple tag example to show how content is added to the
* output stream when a tag is encountered in a JSP page.
*/
public class Hello extends TagSupport {
private String name=null;
/**
* Getter/Setter for the attribute name
as defined in the tld file
* for this tag*/
public void setName(String value){
name = value;
}
public String getName(){
return(name);
}
/**
* doStartTag is called by the JSP container
when the tag is encountered */
public int doStartTag() {
try {
JspWriter out = pageContext.getOut();
out.println("<table border=\"1\">");
if (name != null)
out.println("<tr><td> Welcome <b>" + name +
"</b> </td></tr>");
else
out.println("<tr><td> Hello World </td></tr></table>");
} catch (Exception ex) {
throw new Error("All is not well in the world.");
}
// Must return SKIP_BODY because we are not
//supporting a body for this
// tag.
return SKIP_BODY;
}
/**
* doEndTag is called by the
JSP container when the tag is closed */
public int doEndTag(){
return EVAL_PAGE;
}
}
I also successfully installed the bundle in my felix console without having any error. Then i written custom tag in my jsp as below
JSP:-
<%#include file="/libs/foundation/global.jsp"%>
<%# page import="com.testcb.TestCustomTag"%>
<%# taglib prefix="mytest" uri="http://cs.test.com/bundles/cq/1.8"%>
<mytest:hello name="sachin"></mytest:hello>
I am getting the like "org.apache.sling.api.scripting.ScriptEvaluationException: org.apache.sling.scripting.jsp.jasper.JasperException: /apps/test/components/content/test/test.jsp(4,0) Unable to load tag handler class "com.cb.Hello" for tag "mytest:hello".
The same code is working fine in my apache tomcat server without having any issue. I am getting the error when i incorporate it in CQ.
What am i doing here? Is there any config i need to do in OSGI console to make it available?
UPDATE:
There was some problem with package name. Now Sling can read my tag handler class after i renamed the package name.
The error "Unable to load tag handler class" also has gone.
Now i am getting error as "org.apache.sling.api.scripting.ScriptEvaluationException: javax.servlet.ServletException: javax.servlet.jsp.JspException: com.testcb.TestCustomTag cannot be cast to javax.servlet.jsp.tagext.Tag"
I have the following dependency in pom.xml
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
</dependency>
And Here is my tld
<?xml version="1.0" encoding="ISO-8859-1" ?>
<taglib 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/web-jsptaglibrary_2_0.xsd" version="2.0">
<description>My tag library123</description>
<tlib-version>1.0</tlib-version>
<short-name>TagLib-Test</short-name>
<uri>http://cs.test.com/bundles/cq/1.0</uri>
<jspversion>2.1</jspversion>
<tag>
<name>testcustomtag</name>
<tagclass>com.testcb.TestCustomTag</tagclass>
<bodycontent>empty</bodycontent>
<info>This is a simple hello tag</info>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
Is there any problem with jsp version?
Please guide me to resolve.
Thanks

The tags.tld file needs to be under the META-INF folder. If you don't have it already you can create one under your resources source folder.

Yes, It is working as expected after i removed tag in pom.xml. This was the cause issues. :)
Thanks !

Related

Class Specified in Method signature of TLD function is not found - JasperException

I am migrating a project from legacy spring to springboot. Project uses Spring Webflow and plan to keep the configurations intact for webflow and port the project to springboot by updating the project structure and adding necessary boot libraries.
Currently stuck with a jasperexception from the embedded tomcat jasper libraries complaining that the class used in the method signature of TLD for a function is not found.
org.apache.jasper.JasperException: The class [javax.servlet.http.HttpServletRequest request] specified in the method signature in TLD for the function [mytld:getAppLink] cannot be found. [javax.servlet.http.HttpServletRequest request]
at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:55) ~[tomcat-embed-jasper-9.0.36.jar!/:9.0.36]
at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:294) ~[tomcat-embed-jasper-9.0.36.jar!/:9.0.36]
at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:81) ~[tomcat-embed-jasper-9.0.36.jar!/:9.0.36]
If I update the function in TLD to a no argument function, it works fine at that point. But moment I have a class to pass in the constructor this error is thrown.
JSP:
<%# include file="/WEB-INF/jsp/includes/includes.jspf" %>
<%# taglib prefix="mytld" uri="MyTldLibrary" %>
<div id="header" class="style-header">
<div id="headm">
</div>
</div>
Here is snippet of my tld:
<?xml version="1.0" encoding="UTF-8" ?>
<taglib 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 web-jsptaglibrary_2_2.xsd"
version="2.0">
<tlib-version>2.2</tlib-version>
<jsp-version>3.0</jsp-version>
<short-name>mytld</short-name>
<uri>MyTldLibrary</uri>
<description>App EL Functions</description>
<function>
<name>getAppLink</name>
<function-class>
com.app.web.tags.AppWebFunctions
</function-class>
<function-signature>
java.lang.String getAppLink(javax.servlet.http.HttpServletRequest request)
</function-signature>
</function>
</taglib>
Here is snippet of function getAppLink from AppWebFunctions class:
/**
* Gets the App link.
*
* #param request the request
* #return the App link
* #throws UnsupportedEncodingException the unsupported encoding exception
*/
public static final String getAppLink(final HttpServletRequest request) throws UnsupportedEncodingException {
//LOGIC to retrieve applink
return appLink;
}
If I update the function in TLD to a no argument function, it works fine at that point. But moment I have a class to pass in the constructor this error is thrown.
That's because <function-signature> does not include any parameter names, only the fully-qualified class name of the parameter's type can be specified.
So, just remove the request parameter name and the TLD should work then.
<function-signature>
java.lang.String getAppLink(javax.servlet.http.HttpServletRequest)
</function-signature>

Coexistence of both thymeleaf and jasper files in Spring Boot application

I tried, in a project both jasper and thymeleaf, but can not coexist, as I would like to use jsp must comment out Spring-boot-starter-thymeleaf depend on the package, so that it can run. Looking for a solution so that both jasper and thymeleaf can co exist. I got a solution on stackoverflow if some one use servlet-context.xml ( Mixing thymeleaf and jsp files in Spring Boot ), where both jasper and thymeleaf coexist. But my requirement is how to include those attributes in pom.xml if I am using spring-boot-starter-web.
I was able to run both HTML and JSP page from embedded jar build inside Spring boot. But if you like to run it independently by copying the Jar in command prompt then you need to copy the JSP page folder structure as it will not be in the jar content and you need to change the pom file little bit so that the jar can add external content to it.
STEP 1: Add Thymeleaf and JSP dependencies
Add below dependencies to your pom.xml file
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
STEP 2: Project structure and file creation
Under source folder src/main/resources create folder templates, under that create sub-folder thymeleaf. And create a html file sample.html(say)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
</head>
<body>
THYMELEAF PAGE: <p th:text="${name}"></p>
</body>
</html>
Under src/main/webapp/WEB-INF create sub-folder views. Under views create a jsp file, sample.jsp(say)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello</title>
</head>
<body>
JSP PAGE: Hello ${name}
</body>
</html>
STEP 3: In your application.properties set thymeleaf view names and JSP configuration for internal view resolution.
#tomcat-connection settings
spring.datasource.tomcat.initialSize=20
spring.datasource.tomcat.max-active=25
#Jasper and thymeleaf configaration
spring.view.prefix= /WEB-INF/
spring.view.suffix= .jsp
spring.view.view-names= views
spring.thymeleaf.view-names= thymeleaf
#Embedded Tomcat server
server.port = 8080
#Enable Debug
debug=true
management.security.enabled=false
STEP 4: Create controller for serving Thymeleaf and JSP pages:
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
#Controller
public class TestController {
#RequestMapping(value="/jasper", method=RequestMethod.GET)
public String newjasper(Map<String, Object> m, String name){
//System.out.print("-- INSIDE JSP CONTROLER ------");
m.put("name", name);
return "views/sample";
}
#RequestMapping(value="/thymeleaf", method=RequestMethod.GET)
public String newthymeleaf(Map<String, Object> m, String name){
//System.out.print("-- INSIDE HTML CONTROLER ------");
m.put("name", name);
return "thymeleaf/sample";
}
}
STEP 5: Some cases you may required to create a configuration class SpringConfig.class (say) for view resolution for JSP pages. But optional, I don't use it in my configuration file.
import org.springframework.web.servlet.view.JstlView;
#Configuration
public class SpringConfig {
#Value("${spring.view.prefix}")
private String prefix;
#Value("${spring.view.suffix}")
private String suffix;
#Value("${spring.view.view-names}")
private String viewNames;
#Bean
InternalResourceViewResolver jspViewResolver() {
final InternalResourceViewResolver viewResolver = new
InternalResourceViewResolver();
viewResolver.setPrefix(prefix);
viewResolver.setSuffix(suffix);
viewResolver.setViewClass(JstlView.class);
viewResolver.setViewNames(viewNames);
return viewResolver;
}
}
STEP 6: Testing application for both jsp and html.
When you hit this url in your browser: http://localhost:8080/thymeleaf?name=rohit . This will open our sample.html file with parameter name in center of page and with this url: http://localhost:8080/jasper?name=rohit will open sample.jsp page with parameter name in center.
from the viewresover javadoc.
Specify a set of name patterns that will applied to determine whether
a view name returned by a controller will be resolved by this resolver
or not.
In applications configuring several view resolvers –for example, one
for Thymeleaf and another one for JSP+JSTL legacy pages–, this
property establishes when a view will be considered to be resolved by
this view resolver and when Spring should simply ask the next resolver
in the chain –according to its order– instead.
The specified view name patterns can be complete view names, but can
also use the * wildcard: "index.", "user_", "admin/*", etc.
Also note that these view name patterns are checked before applying
any prefixes or suffixes to the view name, so they should not include
these. Usually therefore, you would specify orders/* instead of
/WEB-INF/templates/orders/*.html.
Specify names of views –patterns, in fact– that cannot be handled by
this view resolver.
These patterns can be specified in the same format as those in
setViewNames(String []), but work as an exclusion list.
viewResolver.setViewNames(viewNames);

Error resolving template "index", template might not exist or might not be accessible by any of the configured Template Resolvers

This question has been asked before but I did not solve my problem and I getting some weird functionality.
If I put my index.html file in the static directory like so:
I get the following error in my browser:
And in my console:
[THYMELEAF][http-nio-8080-exec-3] Exception processing template "login":
Exception parsing document: template="login", line 6 - column 3
2015-08-11 16:09:07.922 ERROR 5756 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].
[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet]
in context with path [] threw exception [Request processing failed; nested
exception is org.thymeleaf.exceptions.TemplateInputException: Exception
parsing document: template="login", line 6 - column 3] with root cause
org.xml.sax.SAXParseException: The element type "meta" must be terminated by
the matching end-tag "</meta>".
However if I move my index.html file into the templates directory I get the following error in my browser:
I have added my view resolvers:
#Controller
#EnableWebMvc
public class WebController extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");
registry.addViewController("/results").setViewName("results");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/form").setViewName("form");
}
#RequestMapping(value="/", method = RequestMethod.GET)
public String getHomePage(){
return "index";
}
#RequestMapping(value="/form", method=RequestMethod.GET)
public String showForm(Person person) {
return "form";
}
#RequestMapping(value="/form", method=RequestMethod.POST)
public String checkPersonInfo(#Valid Person person, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "form";
}
return "redirect:/results";
}
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("templates/");
//resolver.setSuffix(".html");
return resolver;
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
WebSecurityConfig.java
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/index").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<meta>
<meta> charset="UTF-8">
<title></title>
</head>
<body>
<h1>Welcome</h1>
<span>Click here to move to the next page</span>
</body>
</html>
At this point I do not know what is going on. Can anyone give me some advice?
Update
I missed a typo in index.html, but I am still getting the same errors
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta> charset="UTF-8">
<title></title>
</head>
<body>
<h1>Welcome</h1>
<span>Click here to move to the next page</span>
</body>
</html>
Check for the name of the
templates
folder. it should be templates not template(without s).
index.html should be inside templates, as I know. So, your second attempt looks correct.
But, as the error message says, index.html looks like having some errors. E.g. the in the third line, the meta tag should be actually head tag, I think.
In the console is telling you that is a conflict with login. I think that you should declare also in the index.html Thymeleaf. Something like:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>k</title>
</head>
I am new to spring spent an hour trying to figure this out.
go to --- > application.properties
add these :
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
this can be resolved by copying the below code in application.properties
spring.thymeleaf.enabled=false
this make me success!
prefix: classpath:/templates/
check your application.yml
If you are facing this issue and everything looks good, try invalidate cache/restart from your IDE. This will resolve the issue in most of the cases.
this error probably is occurred most of the time due to missing closing tag. and further you can the following dependency to resolve this issue while supporting legacy HTML formate.
as it your code charset="UTF-8"> here is no closing for meta tag.
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
For me the issue was because of Case sensitivity. I was using ~{fragments/Base} instead of ~{fragments/base} (The name of the file was base.html)
My development environment was windows but the server hosting the application was Linux so I was not seeing this issue during development since windows' paths are not case sensitive.
The error message might also occur, if the template name starts with a leading slash:
return "/index";
In the IDE the file was resolved successfully with a path with two slashes:
getResource(templates//index.html)
Delegating to parent classloader org.springframework.boot.devtools.restart.classloader.RestartClassLoader#2399ee45
--> Returning 'file:/Users/andreas/git/my-project/frontend/out/production/resources/templates//index.html'
On the productive system, where the template is packed into a jar, the resolution with two slashes does not work and leads to the same error message.
✅ Omit the leading slash:
return "index";
Adding spring.thymeleaf.mode=HTML5 in the application.properties worked for me. You could try that as well.
I also faced TemplateResolver view error , Adding the spring.thymeleaf.mode=HTML5 in the application.properties worked for me. In case of build created in STS and running for Websphere 9 ..
Check the html file is available in src/main/resources/templates folder
Try adding #RestController as well,
I was facing this same problem, i added both #RestController #Controller, it worked find
It May be due to some exceptions like (Parsing NUMERIC to String or vise versa).
Please verify cell values either are null or do handle Exception and see.
Best,
Shahid
I wasted 2 hours debugging this issue.
Althought I had the template file in the right location (within resources/templates/), I kept getting the same error.
It turns out it was because I had created some extra packages in my project. For instance, all controller files were in 'controller' package.
I did the same thing for the files which were automatically generated by Spring Initializr.
I don't understand exactly why this happens,
but when I moved the ServletInitializer file and the one annotated with #SpringBootApplication back to the root of the project, the error went away !
For me, including these in the pom.xml CAUSES the exception. Removing it from the pom.xml resolves the issue.
(Honestly, I don't know how that happen)
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>${project.build.outputDirectory}</targetPath>
<includes>
<include>application.properties</include>
</includes>
</resource>
</resources>
</build>
In my case I had everything else right as suggested above but still it was complaining that "template might not exist or might not be accessible by any of the configured Template Resolvers". On comparing my project with some other sample projects which were working fine, I figured out I was missing
<configuration>
<addResources>true</addResources>
</configuration>
in spring-boot-maven-plugin. Adding which worked for me. So my plugins section now looks like
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
I am not sure why I needed to add tag to get thymeleaf working though.
I tried all the solutions here and none of them seemed to be working for me
So I tried changing the return statement a little bit and it worked!
Seems like the issue with thymleaf not being able to recognize the template file, adding ".html" in the return statement seemed to fix this
#RequestMapping(value="/", method = RequestMethod.GET)
public String getHomePage(){
return "index.html";
}

Access a static file saved inside the WEB-INF folder of a Servlet from the doPost() method

I'm trying to access some images that I have saved in my Servlet inside the src/main/webapp/WEB-INF/imgs folder, this way:
link
I tryed to access these images with this code within the doPost method of my Servlet:
InputStream s = this.getClass().getClassLoader().getResourceAsStream("http://cardimgs.org/imgs/cardbackground1.jpg");
Movie movie = new Movie();
try {
byte[] bytes = IOUtils.toByteArray(s);
movie.setImage(bytes);
} catch (IOException e) {
e.printStackTrace();
}
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
// Store the image in App Engine's datastore
pm.makePersistent(movie);
} finally {
pm.close();
}
Movie class:
link 2,
the reason why I used the specified urlto create the InputStream is because in my appengine-web.xml I have:
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>myApplicationId</application>
<version>1</version>
<threadsafe>true</threadsafe>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties" />
</system-properties>
<static-files>
<include path="/**.jpg"/>
<include path="/imgs" >
<http-header name="Access-Control-Allow-Origin" value="http://cardimgs.org" />
</include>
</static-files>
</appengine-web-app>
where I try to define my static files.
My problem is, though, that with the above code I can't access my file and fill the InputStream, which appears to be null, therefore returning a NullPointerException.
How can I successfully access my static files?
ClassLoader.getResourceAsStream() loads resources using *the class loader**. It thus looks for them in the classpath. The classpath of a web-app is constitued from WEB-INF/classes and from all the jars inside WEB-INF/lib. The path it expects looks like com/mycompany/myproject/somefile.txt. It can't be a HTTP URL.
What you need is ServletContext.getResourceAsStream("/WEB-INF/imgs/cardbackground1.jpg"). This method loads a resource from anywhere inside the web-app.

Spring Configuration of Custom Apache Camel Data Format

I am using Apache Camel 2.9.2 and Spring 3.0.6.RELEASE. I am trying to use a custom DataFormat to marshal and unmarshal Camel messages. I want to configure my custom DataFormat into one of my routes using Spring.
Apache Camel's documentation states that in order to hook up my custom Data Format to a route in Spring I simply need to declare my custom DataFormat as a bean and reference it inside of my Spring route like so:
<marshal>
<custom ref="myCustomDataFormat"/>
</marshal>
http://camel.apache.org/custom-dataformat.html
So I have the following setup:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<bean id="myCustomDataFormat" class="com.test.CustomDataFormat"/>
<!-- Camel Context -->
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:/test?initialDelay=4000&delay=1000"/>
<marshal>
<custom ref="myCustomDataFormat"/>
</marshal>
<to uri="file:C:/test2"/>
</route>
</camelContext>
</beans>
But when I try to start Camel, I get the following nasty error:
org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'com.test.CustomDataFormat' to required type 'org.apache.camel.model.DataFormatDefinition'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [com.test.CustomDataFormat] to required type [org.apache.camel.model.DataFormatDefinition]: no matching editors or conversion strategy found
My Data Format is defined as follows:
package com.test;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
public class CustomDataFormat implements DataFormat {
/* (non-Javadoc)
* #see org.apache.camel.spi.DataFormat#marshal(org.apache.camel.Exchange, java.lang.Object, java.io.OutputStream)
*/
#Override
public void marshal(Exchange exchange, Object graph, OutputStream stream)
throws Exception {
System.out.println("Marshal");
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, graph);
stream.write(bytes);
}
/* (non-Javadoc)
* #see org.apache.camel.spi.DataFormat#unmarshal(org.apache.camel.Exchange, java.io.InputStream)
*/
#Override
public Object unmarshal(Exchange exchange, InputStream stream)
throws Exception {
System.out.println("Unmarshal");
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, stream);
return bytes;
}
}
I know that my CustomDataFormat implementation is correct because I created the following test route in Java and it worked flawlessly
package com.test;
import org.apache.camel.spring.SpringRouteBuilder;
public class TestFormatRoute extends SpringRouteBuilder {
/* (non-Javadoc)
* #see org.apache.camel.builder.RouteBuilder#configure()
*/
#Override
public void configure() throws Exception {
from("file:C:/test?initialDelay=4000&delay=1000").unmarshal(new CustomDataFormat()).to("file:C:/test2");
}
}
What am I missing?
Thanks
Update
After letting Camel completely start up after receiving this error I found to my disbelief that my custom data format actually does work in the route that I created. I'm not sure what process is attempting to parse my custom data format and failing but it is apparently not the same process parsing the data format to put into my route.
This solves the functional requirement of the data format, but it does not explain why I am receiving this error.
I have also confirmed that it was not the name of my data format (CustomDataFormat) that was causing the issue. Renaming my DataFormat to a unique name (MerlinDataFormat) did not fix the error.
I still would like to know why I am receiving this error since large blocks of ugly red errors in my console and log files aren't exactly appealing.
Thanks again.
It turned out to be a pretty simple solution (and one that I admit should have been easy to see). There are actually two ways to go about solving this issue, one of them using only spring and one of them requiring an additional java class.
Solution 1
Create a new class extending DataFormatDefinition which has the same properties as your custom DataFormat. Override the configureDataFormat() method to set all of the properties of the underlying DataFormat. Add constructor(s) to set the underlying DataFormat as an instance of your CustomDataFormat. Now you should be able to create an instance of your DataFormatDefinition in spring and reference it when marshaling or unmarshaling.
Solution 2 (Quick & Dirty)
In spring, create a new DataFormatDefinition bean and set it's dataFormat property as a reference to your DataFormat spring bean. Now you should be able to reference your DataFormatDefinition bean when marshaling or unmarshaling.
Not really sure what's wrong with your example, it seems just fine. Can you post your code for the data format? Are you implementing org.apache.camel.spi.DataFormat correctly?
I just set up this example with Camel 2.9.2 and it works like a charm. The Custom data format is the one from Camel documentation/source code.
<bean id="mySweetDf" class="com.example.MySweetDf"/>
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:C:/temp/test?initialDelay=4000&delay=1000"/>
<marshal>
<custom ref="mySweetDf"/>
</marshal>
<convertBodyTo type="java.lang.String"/>
<to uri="file:C:/temp/test2"/>
</route>
</camelContext>
data format java file:
package com.example;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
public class MySweetDf implements DataFormat {
public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, graph);
String body = reverseBytes(bytes);
stream.write(body.getBytes());
}
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
byte[] bytes = exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, stream);
String body = reverseBytes(bytes);
return body;
}
private String reverseBytes(byte[] data) {
StringBuilder sb = new StringBuilder(data.length);
for (int i = data.length - 1; i >= 0; i--) {
char ch = (char) data[i];
sb.append(ch);
}
return sb.toString();
}
}
UPDATE
Just tried you code. Seems to work as well. Created a fresh camel 2.9.2 project via mvn archetype 168: remote -> org.apache.camel.archetypes:camel-archetype-spring (Creates a new Camel project with added Spring DSL support.). This does only include camel-core and camel-spring dependencies, nothing else.
Then replaced camel-context.xml with your xml and added your data format code in the java directory. A run with "mvn camel:run" copied the file and printed "marshal" in the log.
[pache.camel.spring.Main.main()] SpringCamelContext INFO Route: route1 started and consuming from: Endpoint[file://C:/test?delay=1000&initialDelay=4000]
[pache.camel.spring.Main.main()] SpringCamelContext INFO Total 1 routes, of which 1 is started.
[pache.camel.spring.Main.main()] SpringCamelContext INFO Apache Camel 2.9.2 (CamelContext: camel-1) started in 0.808 seconds
Marshal
Are you sure you have all dependencies setup correctly and not some .jar file that messes things up with Data formats?
UPDATE2
Okay, I think I have an idea what it is:
http://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/model/dataformat/CustomDataFormat.html
Camel already have a class named as your data format. You should try rename it to something else. CustomDataFormat extends org.apache.camel.model.DataFormatDefinition which is referred to in your error. Java should handle this, since it's two different namespaces, but there might be some issue in your project setup that causes this conflict. Try to rename the data format and see if that solves the problem.
I too was facing the same issue with camel 2.10.0. If you provide the ref with an instance of type org.apache.camel.model.DataFormatDefinition everything works fine!! I can see two classes for xmljson conversion --> XmlJsonDataFormat implementing both DataFormat and DataFormatDefinition.
I solved the same issue that I too was facing.
Implemented a class extending DataFormatDefintion - which in it's configureDataFormat method sets injectable properties for the class that extends DataFormat (in your case this is CustomDataFormat).
I used XmlJson conversion as a template to solve.

Resources