Vraptor 3 e thread

Opa galera, blz?

Então estou implementando um sistema usando o VRaptor e gostaria de saber como faço para que quando alguém fizer o cadastro, ele pega algumas informações do usuário e faz uma busca com essas informações em uma url específica e redireciona para a página de login, mas eu gostaria de saber como faço para que isso seja feito por uma thread em paralelo com o que o usuário fizer. Isso para que o sistema não fique esperando terminar essa requisição. Outra dúvida é como fazer para que ele faça essa busca na url, por exemplo de 10 em 10 minutos e toda vez que o servidor cair e voltar ele faça a busca imediatamente quando iniciar o tomcat.

Milton,

Pelo que você descreveu … fica meio dificel de saber qual a real lógica de negocio você quer realizar…

Mas o fato de você descrever que quer que algo aconteça de tempos em tempos… automaticamente… me remete à tarefa agendada…

Procure por frameworks de agendamento de tarefas como o Quartz… podem ser rodados sem problema algum junto com o VRaptor.

No blog do Daniel tem uma matéria bem interessante e informativa com exemplo de uso:
http://dkist.k2studio.com.br/2010/05/12/introducao-ao-quartz/

Abrs.

[quote=guivirtuoso]Milton,

Pelo que você descreveu … fica meio dificel de saber qual a real lógica de negocio você quer realizar…

Mas o fato de você descrever que quer que algo aconteça de tempos em tempos… automaticamente… me remete à tarefa agendada…

Procure por frameworks de agendamento de tarefas como o Quartz… podem ser rodados sem problema algum junto com o VRaptor.

No blog do Daniel tem uma matéria bem interessante e informativa com exemplo de uso:
http://dkist.k2studio.com.br/2010/05/12/introducao-ao-quartz/

Abrs.[/quote]

É isso mesmo cara, obrigado pela resposta. Agora como que eu faço para iniciar uma classe junto com o tomcat? Tipo essa classe tem o método que faz a busca no site, e gostaria de saber como faço para que ela carregue junto com o tomcat em caso de queda de energia ou algo assim.

Olá Milton,

Já que você está utilizando o VRaptor, você pode anotar uma Classe como @ApplicationScoped e @Component,
e nela criar um metodo anotado com @PostConstruct.

Este metodo será executado logo que o VRaptor iniciar esta classe para deixa-la disponivel como um componente da Aplicação.

package br.com.myapp.infra;

import javax.annotation.PostConstruct;

import br.com.caelum.vraptor.ioc.ApplicationScoped;
import br.com.caelum.vraptor.ioc.Component;

@ApplicationScoped
@Component
public class InitApplication {
	
	@PostConstruct
	public void init() {
		System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  Objeto Instanciado!");
	}

}

Nao sei se é a melhor aplicação, mas é funcional.

Os colegas com mais experiencia podem dar seu aval ou sugestões de uma implementação mais robusta para estes casos de execução na inicialização do projeto.

:wink:

Blz guivirtuoso,

agora voltando a questão da thread. Eu implementei da seguinte maneira: quando o usuario faz o cadastro ele redireciona para ObjectController que cria a thread e da o start e redireciona para o formulário de login. Assim o usuário não fica esperando o retorno do método search que demora um pouco. Segue o código abaixo.

UserController.java


@Resource
public class UsersController { 
	
	private final UserDAO dao; 
	private final Result result; 
	private final Validator validator;
	private final WebUser webUser;
	
	
	public UsersController(UserDAO dao, Result result, Validator validator, WebUser webUser) { 
		this.dao = dao;
		this.result = result; 
		this.validator = validator;
		this.webUser = webUser;
	} 
	
	@Post @Path("/users") 
	public void add(User user) {
		if (dao.existUser(user)) { 
			validator.add(new ValidationMessage("Email já existe", "user.email"));
		} 
		validator.onErrorUsePageOf(UsersController.class).newUser();
		
		dao.add(user);
		
	    result.redirectTo(ObjectsController.class).callsearch(user);
		
	}

ObjectsController.java


@Resource
public class ObjectsController {
	
	
	private final ObjectDAO dao;
	private final Result result;
	private final Validator validator;
	private final SearchThread st;

	
	public ObjectController(ObjectDAO dao,Result result, Validator validator, SearchThread st){
		this.dao = dao;
		this.result = result;
		this.validator = validator;
		this.st = st;
	
	}
	
	public void callsearch(User user){
		
		Runnable runnable = this.st; 
	    
	    // Cria uma thread que recebe o comportamento do objeto runnable
	    Thread thread = new Thread(runnable);
	    
	    // Inicia a thread
	    thread.start();
	    
	    result.redirectTo(UsersController.class).loginForm();
	  
	}

SearchThread.java


@Component
public class SearchThread implements Runnable{
	
	
	private final Session session;
	private final ObjectDAO dao;
	private final Result result;
	
	public SearchThread(ObjectDAO dao, Session session, Result result){
		
		this.session = session;
		this.dao = dao;
		this.result = result;
	}
	public void run(){
		ArrayList<Object> objs = new ArrayList<Object>();
		User userLast = null;
		
		try {
			userLast = loadLast();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		objs = search(userLast);
		
		
		for (Object obj : objs){
			dao.save(obj);
			
		}
		
		
	}
	
	public User loadLast() throws Exception {  
	    Criteria criteria = this.session.createCriteria( User.class );  
	    criteria.addOrder( Order.desc( "ID" ) ).setMaxResults(1);  
	    return ( User ) criteria.uniqueResult();  
	}
        
         public ArrayList<Submission> search(User user){
          ...

A thread é chamada direitinho, se que o problema é que quando vai salvar ele diz que a session está fechada e assim não guarda no banco! Como eu poderia resolver esse problema? Já tentei de várias formas e nada.

Exception in thread "Thread-12" org.hibernate.SessionException: Session is closed!
	at org.hibernate.impl.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:72)
	at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1347)
	at tecnico.dao.ObjectDAO.save(ObjectDAO.java:24)
	at br.com.myapp.SearchThread.run(SearchThread.java:50)
	at java.lang.Thread.run(Thread.java:680) 

PS: O problema é bem nessa linha: dao.save(obj) no run da SearchThread.

Como você está controlando suas Sessões ???

Se estiver deixando que o Spring controle p/ você as Transações, basta anotar o método que precisa de uma transação com @Transactional.

public SearchThread(ObjectDAO dao, Session session, Result result){  
           
         this.session = session;  
         this.dao = dao;  
         this.result = result;  
     }  

    @Transactional
     public void run(){  
         // ..... restante do codigo
     }
}

Lembrando que, para o Spring controle as Transações e as Sessões para você no VRaptor, basta registrar o seguinte pacote no seu web.xml

&lt;context-param&gt;
    &lt;param-name&gt;br.com.caelum.vraptor.packages&lt;/param-name&gt;
    &lt;param-value&gt;br.com.caelum.vraptor.util.hibernate&lt;/param-value&gt;
&lt;/context-param&gt;

:wink:

Então cara, eu não utilizo o Spring não. Tentei anotar o run com o @Transactional e ele reconhece um @Transaction que é um plugin jpa, seria esse? Se não, tem outra maneira sem Spring? Vlw

então, se vc usa o plugin do vraptor (pacote …util.hibernate) ele só controla as transações por requisição. Abre no começo e fecha no final da requisição.

o que vc pode fazer é criar jobs que chamam urls protegidas da sua aplicação. Eu expliquei mais ou menos nesse outro tópico:
http://www.guj.com.br/java/255750-como-trabalhar-com-componentes-e-jobs-no-vraptor-com-taskscheduler-spring

Milton,

O Transactional é o do Spring: org.springframework.transaction.annotation.Transactional

Como você não controla suas transações pelo Spring a abordagem deve ser outra.

Acompanhei o topico mencionado pelo Lucas, e vale a pena. Encaixa-se perfeitamente com seu caso.

Abraços.

como que eu faço o job, nessa questão da thread, para chamar por exemplo o adiciona do meu ObjectController? Eu já tentei fazer minha SearchThread anotada como @Resource como se fosse outro controller, mas quando faço a chamada de result.redirectTo(ObjectsController.class).adiciona(object); causa o seguinte erro:

Exception in thread "Thread-6" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultLogicResult': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:339)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:263)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1083)
	at br.com.caelum.vraptor.ioc.spring.SpringBasedContainer.instanceFor(SpringBasedContainer.java:86)
	at br.com.caelum.vraptor.core.DefaultResult.use(DefaultResult.java:57)
	at br.com.caelum.vraptor.core.AbstractResult.redirectTo(AbstractResult.java:46)
	at tecnico.SearchThread.run(SearchThread.java:61)
	at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
	at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
	at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:40)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:325)

Qual o Escopo da sua classe SearchThread ??? receio que você não possa usar redirect se não for escopo de Request… no caso o escopo padrão de um Componente não anotado no VRaptor.

A partir do momento que você muda o escopo do seu componente, alguns recursos não estarão mais acessiveis naturalmente… pelo menos é oq dá a entender o erro…

Acho que também você deve rever se a abordagem adotada p/ esse caso de tentar fazer esses redirects ai (mesmo q fosse possivel) é aplicável.

:wink:

[quote=guivirtuoso]Qual o Escopo da sua classe SearchThread ??? receio que você não possa usar redirect se não for escopo de Request… no caso o escopo padrão de um Componente não anotado no VRaptor.

A partir do momento que você muda o escopo do seu componente, alguns recursos não estarão mais acessiveis naturalmente… pelo menos é oq dá a entender o erro…

Acho que também você deve rever se a abordagem adotada p/ esse caso de tentar fazer esses redirects ai (mesmo q fosse possivel) é aplicável.

:wink: [/quote]

Então Guilherme, minha classe não está anotada, logo está em escopo padrão, ou seja, de request mesmo. Não entendo esse erro, olha a minha classe:


package mypkg;

//imports ..
@Resource
public class SearchThread implements Runnable{
	
	
	private final Session session;
	private final SubmissionDAO dao;
	private final Result result;
	
	public SearchThread(ObjectDAO dao, Session session, Result result){
		
		this.session = session;
		this.dao = dao;
		this.result = result;
	}
	
	public void run(){
		ArrayList&lt;Object&gt; objs = new ArrayList&lt;Object&gt;();
		User userLast = null;
		
		try {
			userLast = loadLast();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		
			
			objs = search(userLast);
		
		
		for (Object obj : objs){
			result.redirectTo(ObjectsController.class).adiciona(object);
			
		}
		
		
	}
	//retorna o último usuario cadastrado
	public User loadLast() throws Exception {  
	    Criteria criteria = this.session.createCriteria( User.class );  
	    criteria.addOrder( Order.desc( "ID" ) ).setMaxResults(1);  
	    return ( User ) criteria.uniqueResult();  
	}
	
	
	public ArrayList&lt;Object&gt; search(User user){ 
          //Faz a busca que falei e retorna o Arraylist

milton, não era isso que eu quis dizer…

o que é pra fazer é criar um controller que tem o código que vc colocu dentro do método run(), e o job chamar esse controller, via HTTP mesmo…

vc pode usar uma biblioteca como a http client, ou usar direto algo como:

new URL("http://localhost:8080/context/urlQueVcEscolheuPraLogica").getContent();

Lucas, vim te agradecer pela resposta e colocar o post em Resolvido. Funcionou perfeitamente aqui. Me desculpe a demora na resposta pois tive uns problemas e agora que vim implementar essa funcionalidade. Valeu mesmo.

Lucas,
estou com problemas para implementar o código em background, que é a minha dúvida inicial. É simples, mas não estou conseguindo. Criei uma classe e estou querendo que ela faça a busca de 1 em 1 minuto, só que dá um erro no startup que eu não sei como resolver.

package mypkg;

//imports

@ApplicationScoped
@Component
public class ScheduledSearchThread implements Runnable{
	
	private Session session;
	
	
	public ScheduledSearchThread(Session session){
		
		this.session = session;
		
	}
	


	@Override
	@PostConstruct
	public void run() {
		
		for (int i =0;; i++) {
			
			
			if(i == 6) break;
			
			ArrayList<Object> objs = new ArrayList<Object>();
			List<User> users = new ArrayList<User>();

			users = loadAll();

			for (User user : users) {

				try {

					objs = search(user);
				} catch (MalformedURLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				for (Object obj : objs) {
					Transaction tx = this.session.beginTransaction();
					this.session.save(submission);
					tx.commit();

				}
			}
			System.out.println("-------Vou durmir 60 segundos!----------");
			try {
				Thread.sleep(60000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public List<User> loadAll(){ 
		return this.session.createCriteria(User.class).list();
	    
	}
	
	
	public ArrayList<Object> search(User user) throws MalformedURLException, IOException{
         //busca e retorna o array de objetos

E o erro que estou recebendo é esse:


23:20:20,488  INFO [BasicConfiguration  ] Using class br.com.caelum.vraptor.ioc.spring.SpringProvider as Container Provider
23:20:20,508  INFO [DefaultSpringLocator] No application context found
23:20:20,619  INFO [WebAppBootstrapFactory] No static WebAppBootstrap found.
23:20:20,620  INFO [BasicConfiguration  ] br.com.caelum.vraptor.scanning = null
07/11/2011 23:20:20 org.apache.catalina.core.StandardContext filterStart
GRAVE: Exception starting filter vraptor
java.lang.NoClassDefFoundError: com/thoughtworks/xstream/converters/SingleValueConverter
	at br.com.caelum.vraptor.core.BaseComponents.<clinit>(BaseComponents.java:195)
	at br.com.caelum.vraptor.scan.ScannotationComponentScanner.addVRaptorStereotypes(ScannotationComponentScanner.java:176)
	at br.com.caelum.vraptor.scan.ScannotationComponentScanner.findStereotypes(ScannotationComponentScanner.java:141)
	at br.com.caelum.vraptor.scan.ScannotationComponentScanner.scan(ScannotationComponentScanner.java:60)
	at br.com.caelum.vraptor.scan.WebAppBootstrapFactory.create(WebAppBootstrapFactory.java:65)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.start(SpringProvider.java:83)
	at br.com.caelum.vraptor.VRaptor.init(VRaptor.java:110)
	at br.com.caelum.vraptor.VRaptor.init(VRaptor.java:103)
	at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:273)
	at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:254)
	at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:372)
	at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:98)
	at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4584)
	at org.apache.catalina.core.StandardContext$2.call(StandardContext.java:5262)
	at org.apache.catalina.core.StandardContext$2.call(StandardContext.java:5257)
	at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
	at java.util.concurrent.FutureTask.run(FutureTask.java:138)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.ClassNotFoundException: com.thoughtworks.xstream.converters.SingleValueConverter
	at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1678)
	at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1523)
	... 20 more
07/11/2011 23:20:20 org.apache.catalina.core.StandardContext startInternal
GRAVE: Error filterStart
07/11/2011 23:20:20 org.apache.catalina.core.StandardContext startInternal
GRAVE: Context [/tecnico] startup failed due to previous errors

Como corrijo isso? Ou tem outra forma mais simples de fazer essa busca com uma única thread em background?

java.lang.ClassNotFoundException: com.thoughtworks.xstream.converters.SingleValueConverter

esse erro geralmente é falta de jar, no caso o do xstream.

Realmente não tem esse jar aqui não. Onde posso baixá-lo?

no site do xstream, ou no zip do VRaptor

Pois é, na verdade tem aqui sim o xstream, será que o erro é a versão? O que está aqui é o xstream-1.3.1.jar.

está na pasta WEB-INF/lib?