Spring SpEL - How to use SpEL to parse a message - java

I'm trying to use spring SpEL to parse a message received in UDP.
Just to understand how to use Spring SpEL I have written this:
context.xml:
<bean id="message" class="springsimulator.Message">
<property name="strMessage" value="$TEST,11,22,33"/>
</bean>
<bean id="nmeamessage" class="springsimulator.NMEAMessage">
<property name="fields" value="#{message.strMessage.split(',')}"/>
</bean>
<bean id="parser" class="springsimulator.SPELParser">
<property name="values">
<map>
<entry key="val1" value="#{nmeamessage.fields[1]}"/>
<entry key="val2" value="#{nmeamessage.fields[2]}"/>
</map>
</property>
</bean>
Message.java:
public class Message {
public String strMessage;
public void setStrMessage(String strMessage) {
this.strMessage = strMessage;
}
}
NMEAMessage:
public class NMEAMessage {
public String[] fields;
public void setFields(String[] fields) {
this.fields = fields;
}
}
Parser:
public class Parser {
Map<String,String> values;
public void setValues(Map<String, String> values) {
this.values = values;
}
public String toString() {
String message = "";
for (Entry<String, String> entry : values.entrySet()) {
message += entry.getKey() + ":" + entry.getValue() + "\n";
}
return message;
}
}
main:
ExpressionParser parser = new SpelExpressionParser();
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
Parser myparser = (Parser) context.getBean("parser");
System.out.println(myparser);
This works, I have parsed my message.
But now, I want to evaluate the SpEL expression in a loop each time I receive a message
while(running) {
socket.receive(message)
//Split message to get fields
//set fields into a map
}
Is there a proper way to do this using SpEL and context.xml ?
Thanks

To parse SpEL expressions at runtime, do something like this:
// Setup the SpEL parser: do this once
SpelParserConfiguration spelParserConfiguration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, getClass().getClassLoader());
ExpressionParser expressionParser = new SpelExpressionParser(spelParserConfiguration);
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
// Parse (compile) the expression: try to do this once
Expression expression = expressionParser.parseExpression(unevaluatedExpression)
// Then within the loop ...
// Supply context, like the value of your namespace
evaluationContext.setVariable(variableName, value);
// Evaluate an expression as many times as you like
Object result = expression.getValue(evaluationContext);

Related

Add QName as string to #XmlMixed#XmlAnyElement(lax = true) list

Sorry for the foggy title, I know it does not tell much.
Please consider the following xsd type definition:
<xsd:complexType name="TopicExpressionType" mixed="true">
<xsd:sequence>
<xsd:any processContents="lax" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="Dialect" type="xsd:anyURI" use="required"/>
<xsd:anyAttribute/>
</xsd:complexType>
Complete XSD: http://docs.oasis-open.org/wsn/b-2.xsd
Corresponding JAXB generated Java class:
package org.oasis_open.docs.wsn.b_2;
import org.w3c.dom.Element;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "TopicExpressionType", propOrder = {
"content"
})
public class TopicExpressionType {
#XmlMixed
#XmlAnyElement(lax = true)
protected List<Object> content;
#XmlAttribute(name = "Dialect", required = true)
#XmlSchemaType(name = "anyURI")
protected String dialect;
#XmlAnyAttribute
private Map<QName, String> otherAttributes = new HashMap<QName, String>();
public List<Object> getContent() {
if (content == null) {
content = new ArrayList<Object>();
}
return this.content;
}
public String getDialect() {
return dialect;
}
public void setDialect(String value) {
this.dialect = value;
}
public Map<QName, String> getOtherAttributes() {
return otherAttributes;
}
}
The first goal is to produce an XML like this with JAXB:
<wsnt:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</wsnt:TopicExpression>
Please note the followings:
The value of the TopicExpression element is basically a query string that refers to QNames. Example: tns:t1/*/t3
The value of the TopicExpression element contains one or more QName like strings (tns:t1). It must be a string as in the example, it cannot be an Element (e.g.: <my-expresseion>tns:t1/*/t3<my-expresseion/>)
The value of the TopicExpression element is an arbitrary string (at least from the schema's perspective, it follows the rules defined here: https://docs.oasis-open.org/wsn/wsn-ws_topics-1.3-spec-os.pdf page 18)
Even though the value is a string, I need to define the corresponding name space declarations. So if I have an expression like this:
tns:t1 then xmlns:tns has to be declared. If my expresseion is tns:t1/*/tns2:t3 then both xmlns:tns and xmlns:tns2 have to be declared.
The second goal is to get the value of TopicExpression on the other side together with the namespace, using JAXB.
I am completely stuck, I don't know how I could implement this. My only idea is to manually build the value for the TopicExpression and somehow tell the marshaller to include the related namespace declaration despite there is no actual element using it.
Update
Example for a complete SOAP request that includes the before mentioned TopicExpression:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>
<Action xmlns="http://www.w3.org/2005/08/addressing">http://docs.oasis-open.org/wsn/bw-2/NotificationProducer/SubscribeRequest</Action>
<MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:57182d32-4e07-4f5f-8ab3-24838b3e33ac</MessageID>
</env:Header>
<env:Body>
<ns3:Subscribe xmlns:ns3="http://docs.oasis-open.org/wsn/b-2" xmlns:ns4="http://www.w3.org/2005/08/addressing" >
<ns3:ConsumerReference>
<ns4:Address>http://my-notification-consumer-url</ns4:Address>
</ns3:ConsumerReference>
<ns3:Filter>
<ns3:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple" xmlns:ns5="http://my.org/TopicNamespace" xmlns:ns6="http://extension.org/TopicNamespace">
ns5:t1/*/ns6:t3
<ns3:TopicExpression/>
</ns3:Filter>
</ns3:Subscribe>
</env:Body>
</env:Envelope>
Not sure, If I have understood your requirement correctly. See if this code sample is helpful for you. If not then maybe try to edit your question a bit and make me understand what exactly you are looking for. I will try to modify and update the code accordingly. Trying with a simple example would be better than providing the complete XSD. Also, look into the following methods: beforeMarshal and afterUnmarshal.
Following is the XML I am trying to marshal and unmarshal
<tns:TopicExpression Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete" xmlns:tns="http://my.org/TopicNamespace">
tns:t1/*/t3
</tns:TopicExpression>
TopicExpressionType.class:
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class TestPojo {
#XmlValue
private String TopicExpression;
#XmlAnyAttribute
private Map<String, Object> anyAttributes = new HashMap<>();
}
Main.class:
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("topic.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(TestPojo.class).createUnmarshaller();
final TestPojo topic = unmarshaller.unmarshal(xmlStreamReader, TestPojo.class).getValue();
System.out.println(topic.toString());
StringWriter stringWriter = new StringWriter();
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("http://my.org/TopicNamespace", "tns");
Marshaller marshaller = JAXBContext.newInstance(TestPojo.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaces);
QName qName = new QName("http://my.org/TopicNamespace","TopicExpression","tns");
JAXBElement<TestPojo> root = new JAXBElement<TestPojo>(qName, TestPojo.class, topic);
marshaller.marshal(root, stringWriter);
String result = stringWriter.toString();
System.out.println(result);
}
}
As you can see as of now I have populated the namespaces map directly. If this is dynamic then you can populate the same in a map and accordingly you can add it while marshaling.
This will provide the following output during the unmarshalling:
TestPojo(TopicExpression=
tns:t1/*/t3
, anyAttributes={Dialect=http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete})
During marshaling:
<tns:TopicExpression xmlns:tns="http://my.org/TopicNamespace" Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Concrete">
tns:t1/*/t3
</tns:TopicExpression>
So the solution I've implemented:
Created a new TopicExpressionType class which has fields not only for the expression but for the namespaces too, used in the expression:
public class TopicExpressionType {
String dialect;
String expression;
List<Namespace> namespaces;
public TopicExpressionType(String dialect, String expression, List<Namespace> namespaces) {
this.dialect = dialect;
this.expression = expression;
this.namespaces = namespaces;
}
public static class Namespace {
String prefix;
String namespace;
public Namespace(String prefix, String namespace) {
this.prefix = prefix;
this.namespace = namespace;
}
public String getPrefix() {
return prefix;
}
public String getNamespace() {
return namespace;
}
}
}
Then implemented an XmlAdapter that is aware of the specifics, knows how to extract namespace prefixes from the expression string and it can read/write namespace declarations on the TopicExpression XML element:
public class TopicExpressionTypeAdapter extends XmlAdapter<Element, TopicExpressionType> {
#Override
public Element marshal(TopicExpressionType topicExpression) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Element element = document.createElementNS("http://docs.oasis-open.org/wsn/b-2", "mns1:TopicExpression");
element.setAttribute("Dialect", topicExpression.getDialect());
element.setTextContent(topicExpression.getExpression());
for (var ns : topicExpression.namespaces) {
element.setAttribute("xmlns:" + ns.prefix, ns.namespace);
}
return element;
}
#Override
public TopicExpressionType unmarshal(Element arg0) throws Exception {
if (arg0.getFirstChild() instanceof Text text) {
var expression = text.getData();
if (expression == null || expression.isBlank())
throw new TopicExpressionAdapterException("Empty content");
// Extract the prefixes from the expression
var namespacePrefixes = new ArrayList<String>();
getNamespacePrefixes(expression, namespacePrefixes);
//Now get the namespaces for the prefixes
var nsMap = new ArrayList<TopicExpressionType.Namespace>();
for (var prefix : namespacePrefixes) {
var namespace = arg0.getAttribute("xmlns:" + prefix);
if (namespace == null || namespace.isBlank())
throw new TopicExpressionAdapterException("Missing namespace declaration for the following prefix: " + prefix);
nsMap.add(new TopicExpressionType.Namespace(prefix, namespace));
}
var dialect = arg0.getAttribute("Dialect");
if (dialect == null || dialect.isBlank())
throw new TopicExpressionAdapterException("Missing Dialect attribute");
return new TopicExpressionType(dialect, expression, nsMap);
} else {
throw new TopicExpressionAdapterException("Unexpected child element type: " + arg0.getFirstChild().getClass().getName());
}
}
public static class TopicExpressionAdapterException extends Exception {
public TopicExpressionAdapterException(String message) {
super(message);
}
}
}
Note: Implementation of the getNamespacePrefixes() method is left out intentionally from this answer.
The last step is to add the following annotation wherever the TopicExpressionType is used in JAXB generated classes:
#XmlJavaTypeAdapter(TopicExpressionTypeAdapter.class)
TopicExpressionType topicExpression;

Application context not loading after Spring 4 upgrade

I'm in the process of upgrading the spring framework version used in our webapp from 3.1.4 to 4.1.8. With the new Spring version, A few of our unit tests are failing because #Autowired is no longer working. This is one of the failing tests:
#ContextConfiguration(locations={"/math-application-context.xml"})
public class MathematicaMathServiceTest extends JavaMathServiceTest{
#Autowired
private KernelLinkPool mathematicalKernelPool;
protected static String originalServiceType = System.getProperty("calculation.math.service.type");
#AfterClass
public static void unsetMathServiceType(){
System.clearProperty("calculation.math.service.type");
}
#BeforeClass
public static void setMathServiceType(){
System.setProperty("calculation.math.service.type","Mathematica");
}
#Test
public void testMathematicaService() throws Exception{
try {
acquireKernelAndExecute(0);
Assert.assertEquals(0, mathematicalKernelPool.getBorrowingThreadsCount());
} catch(UnsatisfiedLinkError e) {
System.out.println("Mathematica not installed. Skipping test");
}catch(Exception ex){
if (!ExceptionFormatter.hasCause(ex, MathServiceNotConfiguredException.class)){throw ex;}
if (System.getProperty(MathService.SERVICE_CONFIGURED_SYSTEM_VARIABLE) != null){
throw ex;
}
logger.error("Cannot execute test. Math service is not configured");
}
}
}
This is the KernelLinkPool class:
public class KernelLinkPool extends GenericObjectPool implements InitializingBean{
private static final int RETRY_TIMEOUT_MS = 5000;
private static final long STARTUP_WAIT_TIME_MS = 10000;
private boolean mathematicaConfigured = true;
private PoolableObjectFactory factory;
// ensures that multiple requests from the same thread will be given the same KernelLink object
private static ThreadLocal<KernelLink> threadBoundKernel = new ThreadLocal<KernelLink>();
// holds the number of requests issued on each thread
private static ThreadLocal<Integer> callDepth = new ThreadLocal<Integer>();
private long maxBorrowWait;
private Integer maxKernels;
private boolean releaseLicenseOnReturn;
private Logger logger = LoggerFactory.getLogger(this.getClass());
// (used only for unit testing at this point)
private Map<String,Integer> borrowingThreads = new ConcurrentHashMap<String,Integer>();
public KernelLinkPool(PoolableObjectFactory factory) {
super(factory);
this.factory = factory;
this.setMaxWait(maxBorrowWait);
}
#Override
public Object borrowObject() throws Exception{
return borrowObject(this.maxBorrowWait);
}
public Object borrowObject(long waitTime) throws Exception {
long starttime = System.currentTimeMillis();
if (!mathematicaConfigured){
throw new MathServiceNotConfiguredException();
}
try{
if (callDepth.get() == null){
callDepth.set(1);
}else{
callDepth.set(callDepth.get()+1);
}
KernelLink link = null;
if (threadBoundKernel.get() != null){
link = threadBoundKernel.get();
}else{
//obtain kernelLink from object pool
//retry when borrowObject fail until
//maxBorrowWait is reached
while(true){
try{
logger.debug("Borrowing MathKernel from object pool");
link = (KernelLink) super.borrowObject();
break;
}catch(KernelLinkCreationException ex){
long timeElapsed = System.currentTimeMillis() - starttime;
logger.info("Failed to borrow MathKernel. Time elapsed [" + timeElapsed + "] ms", ex);
if(timeElapsed >= waitTime){
logger.info("Retry timeout reached");
throw ex;
}
Thread.sleep(RETRY_TIMEOUT_MS);
}
}
logger.debug("borrowed [" + link + "]");
threadBoundKernel.set(link);
}
borrowingThreads.put(Thread.currentThread().getName(),callDepth.get());
return link;
}catch(Exception ex){
logger.error("Failed to acquire Mathematica kernel. Borrowing threads [" + borrowingThreads + "]");
throw ex;
}
}
public void returnObject(Object obj) throws Exception {
callDepth.set(callDepth.get()-1);
if (callDepth.get() <= 0){
threadBoundKernel.set(null);
borrowingThreads.remove(Thread.currentThread().getName());
if (releaseLicenseOnReturn){
// will destroy obj
super.invalidateObject(obj);
}
else{
// will park obj in the pool of idle objects
super.returnObject(obj);
}
}else{
borrowingThreads.put(Thread.currentThread().getName(),callDepth.get());
}
}
#Override
public void afterPropertiesSet() throws Exception {
try{
if (maxKernels == 0){
List<KernelLink> links = new ArrayList<KernelLink>();
while (true){
try{
links.add((KernelLink)factory.makeObject());
}catch(KernelLinkCreationException ex){
break;
}
}
if(links.isEmpty()){
logger.warn("No available Mathematica license!");
mathematicaConfigured = false;
return;
}
for (KernelLink link : links){
factory.destroyObject(link);
}
logger.info("Detected number of available Mathematica license = [" + links.size() + "]");
setMaxActive(links.size());
setMaxIdle(links.size());
}else{
if(maxKernels < 0){
logger.info("Set number of Mathematica license to no limit");
}else{
logger.info("Set number of Mathematica license to [" + maxKernels + "]");
}
setMaxActive(maxKernels);
setMaxIdle(maxKernels);
}
Object ob = borrowObject(STARTUP_WAIT_TIME_MS);
returnObject(ob);
mathematicaConfigured = true;
}catch(Throwable ex){
logger.warn("Mathematica kernel pool could not be configured: ", ex.getMessage());
mathematicaConfigured = false;
}
}
public int getBorrowingThreadsCount() {
return borrowingThreads.size();
}
public Integer getMaxKernels() {
return maxKernels;
}
public void setMaxKernels(Integer maxKernels) {
this.maxKernels = maxKernels;
}
public boolean isMathematicaConfigured(){
return mathematicaConfigured;
}
public boolean isReleaseLicenseOnReturn() {
return releaseLicenseOnReturn;
}
public void setReleaseLicenseOnReturn(boolean releaseLicenseOnReturn) {
this.releaseLicenseOnReturn = releaseLicenseOnReturn;
}
public long getMaxBorrowWait() {
return maxBorrowWait;
}
public void setMaxBorrowWait(long maxBorrowWait) {
this.maxBorrowWait = maxBorrowWait;
}
}
The tests are failing with this exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.etse.math.wolfram.KernelLinkPool] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
This is the math-application-context file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.1.xsd">
<beans profile="unitTest,integratedTest,activeServer">
<bean class="org.springframework.jmx.export.MBeanExporter"
lazy-init="false">
<property name="registrationBehaviorName" value="REGISTRATION_IGNORE_EXISTING" />
<property name="beans">
<map>
<entry key="etse.math:name=MathematicalKernelFactory"
value-ref="mathematicalKernelFactory" />
<entry key="etse.math:name=MathematicalKernelPool" value-ref="mathematicalKernelPool" />
</map>
</property>
</bean>
<bean id="mathService" class="com.etse.math.MathServiceFactoryBean">
<property name="mathServiceType" value="${calculation.math.service.type}"/>
<property name="mathematicaService" ref="mathematicaService"/>
</bean>
<bean id="mathematicaService" class="com.etse.math.wolfram.MathematicaService">
<property name="kernelPool" ref="mathematicalKernelPool" />
<property name="minParallelizationSize" value="${calculation.mathematica.kernel.parallel.batch.size}" />
</bean>
<bean id="mathematicalKernelPool" class="com.etse.math.wolfram.KernelLinkPool"
destroy-method="close">
<constructor-arg ref="mathematicalKernelFactory" />
<property name="maxKernels" value="${calculation.mathematica.max.kernels}" />
<property name="maxBorrowWait"
value="${calculation.mathematica.kernel.borrow.max.wait}" />
<property name="releaseLicenseOnReturn"
value="${calculation.mathematica.kernel.release.license.on.return}" />
</bean>
<bean id="mathematicalKernelFactory" class="com.etse.math.wolfram.KernelLinkFactory">
<property name="debugPackets" value="false" />
<property name="linkMode" value="launch" />
<property name="mathematicaKernelLocation" value="${calculation.mathematica.kernel.location}" />
<property name="mathematicaLibraryLocation" value="${calculation.mathematica.library.location}" />
<property name="mathematicaAddOnsDirectory" value="${calculation.mathematica.addons.directory}" />
<property name="linkProtocol" value="sharedMemory" />
</bean>
</beans>
<beans profile="passiveServer,thickClient,tools">
<bean id="mathService" class="com.etse.math.DummyMathService"/>
</beans>
I also tried using the application context to load the bean, but that failed with the following exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'mathematicalKernelPool' is defined
If I remove the autowired field, the test fails with a NoSuchBeanDefinitionException for another bean (mathService) that is loaded via the application context in a super class. So it appears that the application context from math-application-context is not loaded for some reason. Any idea of what could be happening here? Thank you.
UPDATE:
I took a look at the beans defined in the application context and confirmed that none of the beans defined in math-application-context are present. The application context contains only beans defined in another context file loaded by the super class. Why would it fail to load math-application-context?
At this point I would honestly get rid of the XML config and go total annotation/code based. Create a Config class and have it create any beans you need to be autowired.
It was a profile issue. The super class to the test was using:
#ProfileValueSourceConfiguration(TestProfileValueSource.class)
to set the profile, but it was not working. After removing that annotation I added:
#ActiveProfiles(resolver=TestProfileValueSource.class) and now its working again.

How to expose a property in MBean description

The following managed operation exists in the project:
#ManagedOperation(description = "Some description")
#ManagedOperationParameters({
#ManagedOperationParameter(name = "key", description = "Some description"),
})
public void foo(String key) {
// some logic
}
Also there is a property which can be used in Spring context by surrounding it with dollar sign and square brackets:
"${some.property.key}"
Is it possible to use the value of aforementioned property key in the managed operation annotation description? Something like:
#ManagedOperationParameter(name = "key",
description = "Some description, please note that the key is ${some.property.key}")
Not out-of-the-box, but it's pretty easy to customize...
public class CustomAttributeSource extends AnnotationJmxAttributeSource implements EmbeddedValueResolverAware {
private StringValueResolver embeddedValueResolver;
#Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
#Override
public ManagedAttribute getManagedAttribute(Method method) throws InvalidMetadataException {
ManagedAttribute managedAttribute = super.getManagedAttribute(method);
if (this.embeddedValueResolver != null) {
managedAttribute
.setDescription(this.embeddedValueResolver.resolveStringValue(managedAttribute.getDescription()));
}
return managedAttribute;
}
#Override
public ManagedOperation getManagedOperation(Method method) throws InvalidMetadataException {
ManagedOperation managedOperation = super.getManagedOperation(method);
if (this.embeddedValueResolver != null) {
managedOperation
.setDescription(this.embeddedValueResolver.resolveStringValue(managedOperation.getDescription()));
}
return managedOperation;
}
}
Then...
<bean class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="foo.CustomAttributeSource" />
</property>
</bean>
</property>
</bean>

How to setup LocalDateTime in Spring XML configuration

My problem relates to the following class:
public class MyClass {
private LocalDateTime startDate;
}
I am trying to setup the startDate property of this bean using Spring XML configuration:
<property name="startDate">
<value>2000-01-01</value>
</property>
I get an error:
Cannot convert value of type [java.lang.String] to required type [java.time.LocalDateTime] for property 'startDate'
Is it possible to use Spring to do this conversion? I found examples on the net how to do that for Date object, however, LocalDateTime doesn't have a constructor taking a string (and the solution seems to need such constructor). LocalDateTime is constructed by using the static method LocalDateTime.parse.
Using the annotations #DateTimeFormat, like:
public class MyClass {
#DateTimeFormat(iso=ISO.LOCAL_DATE_TIME)
private LocalDateTime startDate;
}
is not a solution, since MyClass has to be available outside Spring.
Thanks in advance
you can register your conversion. like the following code, the following will convert a string to LocalDateTime
class CustomLocalDateTimeEditor extends PropertyEditorSupport {
private final boolean allowEmpty;
private final int exactDateLength;
public CustomLocalDateTimeEditor( boolean allowEmpty) {
this.allowEmpty = allowEmpty;
this.exactDateLength = -1;
}
public CustomLocalDateTimeEditor(boolean allowEmpty, int exactDateLength) {
this.allowEmpty = allowEmpty;
this.exactDateLength = exactDateLength;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (this.allowEmpty && !StringUtils.hasText(text)) {
setValue(null);
}
else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
throw new IllegalArgumentException("Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
}
else {
try {
setValue(LocalDateTime.parse(text));
}
catch (DateTimeParseException ex) {
throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
}
}
}
#Override
public String getAsText() {
LocalDateTime value = LocalDateTime.parse(String.valueOf(getValue()));
return (value != null ? value.toString() : "");
}
}
Here is what I ended up with based on Javy's comment:
import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class CustomLocalDateTimeEditor extends PropertyEditorSupport {
public CustomLocalDateTimeEditor() {
}
private LocalDateTime parseText(String text) {
LocalDateTime ldt;
try {
ldt = LocalDateTime.parse(text);
} catch(Exception ee) {
ldt = null;
}
if(ldt == null) {
try {
ldt = LocalDateTime.of(LocalDate.parse(text), LocalTime.of(0, 0));
} catch(Exception ee) {
ldt = null;
}
}
return ldt;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(parseText(text));
}
#Override
public String getAsText() {
LocalDateTime value = parseText(String.valueOf(getValue()));
return (value != null ? value.toString() : "");
}
}
And in the XML I have the following:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.time.LocalDateTime" value="com.mycompany.CustomLocalDateTimeEditor" />
</map>
</property>
</bean>
<bean id="myClass" class="MyClass">
<property name="startDate">
<value>2000-01-01</value>
</property>
</bean>
You can use #DateTimeFormat annotation and provide a pattern. For example
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDateTime startDate;
When Spring find the Joda localdatetime, it will convert date in appropriate format.
#DateTimeFormat in spring
Declare a dateFormat bean, in “MyClass” bean, reference “dateFormat” bean as a factory bean. The factory method will call SimpleDateFormat.parse() to convert String into Date object automatically.
reference:
http://www.mkyong.com/spring/spring-how-to-pass-a-date-into-bean-property-customdateeditor/

ResourceManager : unable to find resource 'emailTemplate.vm' in any resource loader

I am doing an application in springs with maven. i wrote all properties in app.properties file
file structure is like this
src/main/resource
|_
| templates
| |_mytempaltefile.vm
|_ app.properties
i gave the path(absloute) in app.property
app.properties file
template.base.path=D\:/SVN/trunk/tfbdirect/src/main/resources/templates
utilities-spring.xml
<bean id="velocityEngine"
class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
<property name="velocityProperties">
<props>
<prop key="resource.loader">file</prop>
<prop key="file.resource.loader.class">
org.apache.velocity.runtime.resource.loader.FileResourceLoader
</prop>
<prop key="file.resource.loader.path">${template.base.path}</prop>
<prop key="file.resource.loader.cache">false</prop>
</props>
</property>
</bean>
my class
import java.util.HashMap;
import java.util.Map;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.ui.velocity.VelocityEngineUtils;
import com.providerpay.tfbdirect.service.mail.MailSenderService;
public class LoginServiceImpl implements ILoginService{
/**
* Injected through Spring IOC
*/
ILoginDAO loginDAO;
ClaimRuleProcessServiceImpl claimRuleProcessServiceImpl;
PlatformTransactionManager txmanager;
//IForgotPasswordDAO forgotPasswordDAO;
private VelocityEngine velocityEngine;
private String appURL;
private MailSenderService mailSenderService;
TFBLogger log = TFBLoggerFactory.getLogger(RuleServer.class);
public String getAppURL() {
return appURL;
}
public void setAppURL(String appURL) {
this.appURL = appURL;
}
public MailSenderService getMailSenderService() {
return mailSenderService;
}
public VelocityEngine getVelocityEngine() {
return velocityEngine;
}
public void setVelocityEngine(VelocityEngine velocityEngine) {
this.velocityEngine = velocityEngine;
}
public void setMailSenderService(MailSenderService mailSenderService) {
this.mailSenderService = mailSenderService;
}
public ILoginDAO getLoginDAO() {
return loginDAO;
}
public void setLoginDAO(ILoginDAO loginDAO) {
this.loginDAO = loginDAO;
}
public ClaimRuleProcessServiceImpl getClaimRuleProcessServiceImpl() {
return claimRuleProcessServiceImpl;
}
public void setClaimRuleProcessServiceImpl(
ClaimRuleProcessServiceImpl claimRuleProcessServiceImpl) {
this.claimRuleProcessServiceImpl = claimRuleProcessServiceImpl;
}
public void setTxmanager(PlatformTransactionManager txmanager) {
this.txmanager = txmanager;
}
/**
* Validates Login
* #param loginView
* #return
*/
public boolean isValidLogin(LoginView loginView) {
/* create tx definition object */
DefaultTransactionDefinition paramTransactionDefinition = new DefaultTransactionDefinition();
TransactionStatus status = txmanager.getTransaction(paramTransactionDefinition );
boolean result = false;
try{
LoginEntity loginEntity = BeanMapper.INSTANCE.viewToEntityMapper(loginView);
Feedback feedback = claimRuleProcessServiceImpl.validateClaimEligibility(loginEntity);
log.info( "Rule executed was " +feedback.getAll());
for (FeedbackMessage feedbackmessaage :feedback.getAll())
{
log.info("\n--------------");
log.info(feedbackmessaage.getRuleCd());
log.info(feedbackmessaage.getMessage());
log.info(feedbackmessaage.getSeverity().getName());
log.info("\n--------------");
}
result = loginDAO.isValidLogin(loginEntity);
log.debug("result = {}", result);
txmanager.commit(status);
}catch(Exception e){
txmanager.rollback(status);
throw new TfbException("Error occured while validating login credentials");
}
return result;
}
#Autowired
VelocityEngine velocityengine;
public boolean mailResetLink(LoginView loginView) {
String toEmailAddress;
LoginEntity loginEntity = BeanMapper.INSTANCE.viewToEntityMapper(loginView);
/* getting user Email from DAO*/
toEmailAddress = loginDAO.getEmailByUsername(loginEntity);
if(toEmailAddress != null && toEmailAddress.trim().length() > 0)
{
Map<String, Object> model = new HashMap<String, Object>();
model.put("user", loginEntity);
model.put("appURL", appURL);
String body = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, "emailTemplate.vm","UTF-8", model);
mailSenderService.sendMail("from mail", toEmailAddress, "Password Reset Link",body);
}
else
{
return false;
}
return true;
}
public boolean resetPassword(LoginView loginView)
{
LoginEntity loginEntity = BeanMapper.INSTANCE.viewToEntityMapper(loginView);
return loginDAO.resetPassword(loginEntity);
}
}
every thing fine but i need to change the absolute path to relative path.. i tried many ways.
i tried like following
template.base.path=/templates/
but still getting below error .
ResourceManager : unable to find resource 'emailTemplate.vm' in any resource loader.
can any one help me..
Thanks in advance
You are falling into a common pitfall when using velocity with spring : you place your templates in one location and use a resource loader that searches them in another place. So you have 2 common usages :
put templates in classpath (as you do) and use ClasspathResourceLoader
resource.loader = class
class.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
it is simple with little dependencies, but it forces you to put templates in classpath ...
put templates under WEB-INF (as you would do for JSPs) and use WebappResourceLoader from velocity tools
resource.loader=webapp
webapp.resource.loader.class=org.apache.velocity.tools.view.WebappResourceLoader
webapp.resource.loader.path=/WEB-INF/velocity/
it is more natural for a template location, but you add a dependency on velocity tools.
And let spring manage dependencies but not instanciating via new ...
I had the same problem recently with karaf OSGi framework.
In a CXF resource class (Login in this context) you have to initialize the Velocity Engine like this:
public Login() {
/* first, get and initialize an engine */
ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
}
Then, in the web method, instantiate the template:
/* create a context and add data */
synchronized (initLock) {
if (loginTemplate == null) {
loginTemplate = ve.getTemplate("templates/login.vm");
}
}
VelocityContext context = new VelocityContext();
Unfortunately, it didn't work out to load the template immediately after the ve.init() call in the constructor.
Your configuration seems correct. If you instantiate the VelocityEngine instance using "new", try following:
#Autowired
VelocityEngine velocityEngine;

Categories