Andrea Girardi - It's my blog!

Tag: Spring 3 (page 1 of 2)

Dynamic for with Spring MVC using a HashMap

Sometime you need to dynamically generate a form without knowing how many fields it will be required (i.e. when your form is driven by a configuration or by some properties). The problem is to draw the form and, return the values to the Controller, and recognize the couples Field name / Field value after the submit..

You can easily get solve this problem just adding HashMap which will hold the key-value pair data to the DataModel.

Supposing your configuration says: you have to draw two fields and these are the name, you UI will be something like*:

<c:forEach items="${newRequest.fields}" var="field">
	<f:input type="text" path="rawFields['${field.field_id}']" class="form-control validate[groupRequired[mandatoryField]]" /> (R)
</c:forEach>

When you submit the form, the values and the key for the dynamic fields will be filled.

* newRequest is the DataModel you are passing and fields is the list of Fields that user will fill with data, like that:

public class Request {
 
	/** Request type */
	private int templateRequest;
 
	// ***** Input field ***** 
	List<RequestField> fields = new ArrayList<RequestField>();
 
	private HashMap<String, Object> rawFields = new HashMap<String, Object>();
 
	[Setters and getters]
 
}

dataSource: The name of the property, following JavaBean naming conventions.

Suppose you defined TemplateDao Spring bean in this way:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">	
	<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>	
	<property name="url"><value>jdbc:mysql://${mysql.hostname}:${mysql.port}/${mysql.db}</value></property>
	<property name="username"><value>${mysql.user}</value></property>
	<property name="password"><value>${mysql.password}</value></property>
</bean>	
 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean> 
 
<bean id="TemplateRequestDao" class="com.afm.admin.dao.mysql.TemplateDaoMySql">
	<property name="dataSource" ref="dataSource" />
</bean>

and you get this error message: The name of the property, following JavaBean naming conventions. The reason is probably you forgot to extend your implementation class:

public class TemplateDaoMySql extends JdbcDaoSupport implements TemplateDao {
 
	@Override
	public List<TemplateRequest> getTemplateRequest(UserProfile userProfile) {
		// TODO Auto-generated method stub
		return null;
	}
 
}

Time lost to fix this issue: more or less 1 hour. Image the how many I was frustrated when I discovered what was the problem.

Get a list of Object from jdbcTemplate()

Sometime happens get a list of object from database and, to do that without call a RowMapper, use this:

List<Object> strings = (List<Object>) jdbcTemplate.queryForList(query, Object.class);

This is a sample to get a list of String:

List<String> strings = (List<String>) jdbcTemplate.queryForList(query, String.class);

Spring-data: Cannot use a complex object as a key value

I was trying to figured out how to solve this issue. Basically I’m saving a user profile bean that contains multiple occurrences of other beans inside him. Something like date:

public class UserProfile {
 
	List<Class1> classes1;
	List<Class2> classes2;
 
	Integer int1;
	Map<String, Class3> classes3;
 
}

I was working fine until I changed a getter adding some business functionalities and surrounding all lines with a try / catch. Of course, I putted the exception message on the log using adding this definition inside the bean:

protected final Log logger = LogFactory.getLog(getClass());

Good point for me, everything worked fine until I updated the object in Mongo. Holy crap, I got the “Cannot use a complex object as a key value” exception and I spent two hours trying to change the beans (of course I did a lot of other changes after the fatal adding of business logic inside my bean). Exhausted, I contacted my colleague Max (@maxfarnea) and he told me, “when I got this error, I removed the log inside a bean”. Ipso facto, I removed the log entry from my bean, added a throws exception instead of a try / catch and tested! Well done @maxfarnea, it works!

I learned two lesson today: one, don’t put log into beans that need to be stored using spring-data and, two, if you are in pain, ask, talk, don’t be silly, don’t waste your time!!! Stackoverflow.com is a good point to start (and my blog either, of course) but never is more helpful than a quick chat with a colleague!

No suitable driver found for jdbc:mysql://localhost:3306/schema

If the exception “Could not get JDBC Connection; nested exception is java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/schema” is raised probably you forgot to add the

<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>

property to your dataSource bean:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">		
	<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
	<property name="url"><value>jdbc:mysql://172.16.0.11:3306/test_vale</value></property>
	<property name="username"><value>root</value></property>
	<property name="password"><value>password</value></property>
</bean>

JSP Error : Attribute qualified names must be unique within an element

Today I updated an old Spring MVC application to Apache Tomcat 7 and some other newer jars and, when I started it, I get this error:

SEVERE: Servlet.service() for servlet [mvc-dispatcher] in context with path [/CG] threw exception [/WEB-INF/view/main.jsp (line: 2, column: 0) /WEB-INF/view/include.jsp (line: 3, column: 75) Attribute qualified names must be unique within an element] with root cause
org.apache.jasper.JasperException: /WEB-INF/view/main.jsp (line: 2, column: 0) /WEB-INF/view/include.jsp (line: 3, column: 75) Attribute qualified names must be unique within an element

After some search i find out that using single attribute multiple time in a single tag throw this error(it works with no problem in previous version!!!)

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

I removed the duplicated “prefix” tag and everything worked fine! (…ehm… everything? Of course not… but this exception has been fixed)

How to download PDF file from url on MVC controller

To download a remote file (like a PDF) redirecting to response output, use these instructions to update a your Spring MVC controller:

@RequestMapping(value="/viewAttach", method = RequestMethod.GET)
public ModelAndView viewAttach(@RequestParam(value="article_id", required = true) String article_ref, HttpSession session, HttpServletResponse response) 
{
	/* *** Remember to check if Session still valid  *** */
	try {
 
		URL url = new URL(remoteURL);        	
		response.setHeader("Content-disposition", "attachment;filename=" + filename);
 
		//Set the mime type for the response
		response.setContentType("application/pdf");
 
		// URLConnection connection = url.openConnection();
		InputStream is = url.openStream();
 
		BufferedOutputStream outs = new BufferedOutputStream(response.getOutputStream());
		int len;
		byte[] buf = new byte[1024];
		while ( (len = is.read(buf)) &gt; 0 ) {
			outs.write(buf, 0, len);
		}
		outs.close();
 
	} catch (MalformedURLException e) {
		logger.error("Error ModelAndView.viewMain - MalformedURLException : " + e.toString() + " -- " + e.getStackTrace()[0].toString());
		return null;
	} catch (IOException e) {
		logger.error("Error ModelAndView.viewMain - IOException : " + e.toString() + " -- " + e.getStackTrace()[0].toString());
		return null;
	}
 
	return null;
 
}

Route a message to MongoDB

The requirement is very simple. Route an XML message from rabbitMQ to MongoDB. MongoDB BSON as the data storage and network transfer format for “documents”. BSON is a binary-encoded serialization of JSON-like documents. So, the source message is in a XML format, after getting it from rabbitMQ is necessary to translate into a JSON format compatible with MongoDB.

To translate a message from XML to JSON is possible to use Marshalling function available in Camel extension XmlJson. The pom.xml file needs this new entry:

<dependency>
	<groupId>org.apache.camel</groupId>
	<artifactId>camel-xmljson</artifactId>
	<version>${org.camel.version}</version>
</dependency>

Of course, to send communicate with MongoDB another entry has to be added to pom.xml:

<dependency>
	<groupId>org.apache.camel</groupId>
	<artifactId>camel-mongodb</artifactId>
	<version>${org.camel.version}</version>
</dependency>

Now, is possible to change the route on camel ApplicationContext file:

<!-- Camel route -->    
<camelContext xmlns="http://camel.apache.org/schema/spring">
	<dataFormats>
		<xmljson id="xmljson"/>
	</dataFormats>
	<route> 
		<from uri="spring-amqp:TPDirect:TPQueue:TPRouting?type=direct&amp;autodelete=true&amp;durable=true" />
		<marshal ref="xmljson"/>
		<log message="From XML to Json: DONE!" />
		<convertBodyTo type="java.lang.String"/>
		<to uri="mongodb:myDb?database=flights&amp;collection=tickets&amp;operation=save" />
	</route>                                 
</camelContext>
 
 
<!-- Mongo DB -->
<bean id="myDb" class="com.mongodb.Mongo">
	<constructor-arg index="0" value="localhost"/>
</bean>

Bean myDB contains the information to reach MongoDB. It’s also possible to define it using the full url:

<bean id="myDb" class="com.mongodb.Mongo"> 
    <constructor-arg index="0"> 
        <bean class="com.mongodb.MongoURI"> 
            <constructor-arg index="0" value="mongodb://username:password@host:port/db" /> 
        </bean> 
    </constructor-arg> 
</bean>

To avoid this exception:

Caused by: No type converter available to convert from type: byte[] to the required type: org.apache.camel.component.mongodb.converters.MongoDbBasicConverters with value

The JSON translated message has to be converted into String.

That’s all!

Camel and RabbitMQ : Finally, how to!

Define a RabbitMQ broker endpoint in Camel is possible with the Bluelock camel-spring-amqp (https://github.com/Bluelock/camel-spring-amqp) library. It’s an Apache Camel component that allow to natively communicate with a RabbitMQ broker and it’s implemented using Spring’s AMQP.

For first, with Eclipse IDE create a new Maven Project with Artifact ID camel-arthetype-spring. This allow to use Spring DSL to configure Camel route and excute the run:camel goal of Camel Mavel Pluing (Camel Maven Plugin) in a forked JVM from Maven.

To resolve the dependencies, these entries are mandatory:

<dependency>
	<groupId>com.bluelock</groupId>
	<artifactId>camel-spring-amqp</artifactId>
	<version>1.2</version>
</dependency>
 
<!-- Camel dependencies -->
<dependency>
	<groupId>org.apache.camel</groupId>
	<artifactId>camel-test</artifactId>
	<version>${org.camel.version}</version>          
</dependency>
<dependency>
	<groupId>org.apache.camel</groupId>
	<artifactId>camel-spring</artifactId>
	<version>${org.camel.version}</version>        
	<exclusions>
		<exclusion>
			<artifactId>spring-tx</artifactId>
			<groupId>org.springframework</groupId>
		</exclusion>
		<exclusion>
			<artifactId>spring-context</artifactId>
			<groupId>org.springframework</groupId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.apache.camel</groupId>
	<artifactId>camel-xstream</artifactId>
	<version>${org.camel.version}</version>            
</dependency>

At this point edit the camel-context.xml available on src/main/resource/META-INF/spring folder:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Configures the Camel Context-->
 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
       http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
 
	<camelContext xmlns="http://camel.apache.org/schema/spring">
		<route>	
			<from uri="spring-amqp:KipcastDirect:KipcastQueue:KipcastRouting?type=direct&amp;autodelete=true&amp;durable=true" />
			<log message="Message available on a RabbitMQ Queue" />			
			<process ref="processorTest" />
		</route>
	</camelContext>
 
	<rabbit:connection-factory id="amqpConnectionFactory" />
	<rabbit:template id="amqpTemplate" connection-factory="amqpConnectionFactory" message-converter="messageConverter" exchange="KipcastBean" />
	<rabbit:admin connection-factory="amqpConnectionFactory"/>
 
	<bean id="amqpConnectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
	    <property name="host" value="10.211.55.20"/>
	    <property name="port" value="5672"/>
	    <property name="username" value="guest"/>
	    <property name="password" value="guest"/>
	    <property name="virtualHost" value="/"/>
	</bean>
 
    <bean id="jsonMessageConverter" class="amqp.spring.converter.XStreamConverter"/>
    <bean id="textMessageConverter" class="amqp.spring.converter.StringConverter"/>
    <bean id="messageConverter" class="amqp.spring.converter.ContentTypeConverterFactory">
        <property name="converters">
            <map>
                <entry key="application/json" value-ref="jsonMessageConverter"/>
                <entry key="application/xml" value-ref="textMessageConverter"/>
            </map>
        </property>
        <property name="fallbackConverter" ref="textMessageConverter"/>
    </bean>
 
</beans>

In this case, a route starting from a RabbitMQ queue to a system log and a listener on your queue that will be active until you terminate your maven came:run process has been created.

It’s very important to note that, on Spring XML & has to be quoted &amp;

The following Java code should be used send a message to the Exchange defined on Camel Route:

@Test
public void test() throws KeyManagementException, NoSuchAlgorithmException, URISyntaxException, IOException {
	ConnectionFactory factory = new ConnectionFactory();
	factory.setHost("10.211.55.20");
	factory.setPort(5672);
	factory.setVirtualHost("/");
	factory.setUsername("guest");
	factory.setPassword("guest");
	Connection connection = factory.newConnection();
	Channel channel = connection.createChannel();
 
	channel.exchangeDeclare("KipcastDirect", "direct", 
		   true, 	/* durable */
		   true, 	/* autodelete */
		   null); 	/* */
 
	byte[] messageBodyBytes = "Hello, world!".getBytes();
 
	AMQP.BasicProperties.Builder basic = new AMQP.BasicProperties.Builder();
	AMQP.BasicProperties minBasic = basic.build();
 
	minBasic = basic.priority(0).deliveryMode(1).build();
 
	channel.basicPublish("KipcastDirect", "KipcastRouting", minBasic, messageBodyBytes);
	System.out.println(" [x] Sent ");
 
	channel.close();
}

To test if listener works, for first run the queue listener and, after that, run the Junit class to send message. The output will be:

[pache.camel.spring.Main.main()] MainSupport                    INFO  Apache Camel 2.10.3 starting
[pache.camel.spring.Main.main()] SpringCamelContext             INFO  Apache Camel 2.10.3 (CamelContext: camel-1) is starting
[pache.camel.spring.Main.main()] ManagementStrategyFactory      INFO  JMX enabled.
[pache.camel.spring.Main.main()] DefaultTypeConverter           INFO  Loaded 177 type converters
[pache.camel.spring.Main.main()] SpringAMQPComponent            INFO  Found AMQP ConnectionFactory in registry for 10.211.55.20
[pache.camel.spring.Main.main()] SpringAMQPComponent            INFO  Found AMQP Template in registry
[pache.camel.spring.Main.main()] SpringAMQPComponent            INFO  Found AMQP Administrator in registry
[pache.camel.spring.Main.main()] SpringAMQPEndpoint             INFO  Creating endpoint for KipcastDirect:KipcastQueue:KipcastRouting
[pache.camel.spring.Main.main()] SpringAMQPConsumer             INFO  Declared exchange KipcastDirect
[pache.camel.spring.Main.main()] SpringAMQPConsumer             INFO  Declared queue KipcastQueue
[pache.camel.spring.Main.main()] SpringAMQPConsumer             INFO  Declaring binding KipcastRouting
[pache.camel.spring.Main.main()] SpringAMQPConsumer             INFO  Started AMQP Async Listeners for spring-amqp://KipcastDirect:KipcastQueue:KipcastRouting?autodelete=true&amp;durable=true&amp;type=direct
[pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route1 started and consuming from: Endpoint[spring-amqp://KipcastDirect:KipcastQueue:KipcastRouting?autodelete=true&amp;durable=true&amp;type=direct]
[pache.camel.spring.Main.main()] ultManagementLifecycleStrategy INFO  StatisticsLevel at All so enabling load performance statistics
[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.10.3 (CamelContext: camel-1) started in 0.505 seconds
[     SimpleAsyncTaskExecutor-1] route1                         INFO  Message available on a RabbitMQ Queue

At this point you can have fun with Camel and RabbitMQ!!!!!

NOTES:

1 – Please be carefoul: the URI (from and to) on Camel Spring DSL context and JUnit class must refer to same Exchange and Queue to prevent a reply-text=PRECONDITION_FAILED – parameters for queue ‘QUEUE’ in vhost ‘/’ not equivalen error or similar. To check the queues / exchanges configuration parameter use:

rabbitmqadmin -V / list queue
rabbitmqadmin -V test list exchanges

if you like this post, please click on the advertise :)

Put SQL result into a map

To extract two or more object (in this sample two Integer) from a DB and putting into a map, use this:

final Map<Integer,Integer> topic = new HashMap<Integer,Integer>();
 
getJdbcTemplate().query(sqlCommand, new Object[] {query_parameter}, new RowMapper<Object>(){
	public Object mapRow(ResultSet rs, int arg1) throws SQLException {
		topic.put(rs.getInt("integer1"), rs.getInt("integer2"));
		return null;
	}
});

To read all values:

Iterator<Map.Entry<Integer, Integer>> users = userScheduledToday.entrySet().iterator();
while ( users.hasNext() ) {
	Map.Entry<Integer, Integer> entry = users.next();
	logger.debug("Key: " + entry.getKey() + " - Value: " + entry.getValue());
}
Olderposts

Copyright © 2017 Andrea Girardi – It's my blog!

Theme by Anders NorenUp ↑