VRaptor - XStream CircularReferenceException

Olá pessoal,
Estou com um problema para serializar minhas entidades no formato JSON.

Controller

this.result.use(json()).indented().from(usuario).exclude("usuarioRegraList").serialize();

Entity Usuario:

@Entity
public class Usuario extends br.com.k2studio.util.jpa.entity.BaseEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@SequenceGenerator(name="USUARIO_ID_GENERATOR", sequenceName="USUARIO_ID_SEQ", allocationSize=1)
	@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="USUARIO_ID_GENERATOR")
	private Integer id;
	
	private String nome;

	@Column(name="chave_usuario")
	private String chaveUsuario;

	private String email;

	private String senha;
	
	@ManyToOne(cascade={CascadeType.PERSIST})
	private Endereco endereco;
	
	@Enumerated(EnumType.ORDINAL)
	private Status status;
	
	@Column(name="data_cadastro")
	private Timestamp dataCadastro;
	
	@Transient
	private String confirmacao;
	
	@OneToMany(mappedBy="usuario", cascade={CascadeType.PERSIST})
	private List<UsuarioRegra> usuarioRegraList;

        //Getters/Setters

}

Entity UsuarioRegra:

@Entity
@Table(name="usuario_regra")
public class UsuarioRegra extends br.com.k2studio.util.jpa.entity.BaseEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@SequenceGenerator(name="USUARIO_REGRA_ID_GENERATOR", sequenceName="USUARIO_REGRA_ID_SEQ", allocationSize=1)
	@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="USUARIO_REGRA_ID_GENERATOR")
	private Integer id;

	@ManyToOne(cascade={CascadeType.PERSIST})
	private Usuario usuario;

	//bi-directional many-to-one association to Regra
        @ManyToOne(cascade={CascadeType.PERSIST})
	private Regra regra;

        //Getters/Setters

}

Stacktrace:

22:29:48,183 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[localhost].[/app].[default]] Servlet.service() for servlet default threw exception: br.com.caelum.vraptor.InterceptionException: exception raised, check root cause for details: com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceException: 
	at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:96) [:]
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:61) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:89) [:]
	at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:83) [:]
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:48) [:]
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.interceptor.ExceptionHandlerInterceptor.intercept(ExceptionHandlerInterceptor.java:71) [:]
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:69) [:]
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54) [:]
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54) [:]
	at br.com.caelum.vraptor.core.EnhancedRequestExecution.execute(EnhancedRequestExecution.java:23) [:]
	at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:92) [:]
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:58) [:]
	at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:89) [:]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:343) [:]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:109) [:]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:97) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:100) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:78) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:35) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:177) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:188) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:79) [:]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:355) [:]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:149) [:]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237) [:3.0.3.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167) [:3.0.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [:6.0.0.Final]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) [:6.0.0.Final]
	at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181) [:6.0.0.Final]
	at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285) [:1.1.0.Final]
	at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261) [:1.1.0.Final]
	at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88) [:6.0.0.Final]
	at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100) [:6.0.0.Final]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) [:6.0.0.Final]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [:6.0.0.Final]
	at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158) [:6.0.0.Final]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [:6.0.0.Final]
	at org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53) [:6.0.0.Final]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362) [:6.0.0.Final]
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [:6.0.0.Final]
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654) [:6.0.0.Final]
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951) [:6.0.0.Final]
	at java.lang.Thread.run(Thread.java:680) [:1.6.0_22]
Caused by: com.thoughtworks.xstream.core.TreeMarshaller$CircularReferenceException: 
	at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:83) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:78) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:157) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:148) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.visit(AbstractReflectionConverter.java:118) [:]
	at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.visitSerializableFields(PureJavaReflectionProvider.java:129) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:100) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:58) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:86) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:78) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshallField(AbstractReflectionConverter.java:157) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.writeField(AbstractReflectionConverter.java:148) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$2.visit(AbstractReflectionConverter.java:118) [:]
	at com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider.visitSerializableFields(PureJavaReflectionProvider.java:129) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doMarshal(AbstractReflectionConverter.java:100) [:]
	at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.marshal(AbstractReflectionConverter.java:58) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.convert(TreeMarshaller.java:86) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:78) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:63) [:]
	at com.thoughtworks.xstream.core.TreeMarshaller.start(TreeMarshaller.java:98) [:]
	at com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy.marshal(AbstractTreeMarshallingStrategy.java:38) [:]
	at com.thoughtworks.xstream.XStream.marshal(XStream.java:837) [:]
	at com.thoughtworks.xstream.XStream.marshal(XStream.java:826) [:]
	at com.thoughtworks.xstream.XStream.toXML(XStream.java:801) [:]
	at br.com.caelum.vraptor.serialization.xstream.XStreamSerializer.serialize(XStreamSerializer.java:237) [:]
	at br.com.k2studio.climp.web.util.json.JsonUtilImpl$1.serialize(JsonUtilImpl.java:66) [:]
	at br.com.k2studio.climp.web.controller.LoginController.doLogin(LoginController.java:45) [:]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [:1.6.0_22]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) [:1.6.0_22]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [:1.6.0_22]
	at java.lang.reflect.Method.invoke(Method.java:597) [:1.6.0_22]
	at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:61) [:]
	... 71 more

[]'s
Daniel

olá yorgan,

vc fez alguma customização do json()? se tirar o indented funciona?

por padrão vc não precisaria do exclude, pq o VRaptor só serializa primitivos (string, numeros e datas) por padrão

o que tem no baseEntity?

Não alterei nada no json().
E mesmo sem o indented o erro persiste.
Agora, quanto a serializar somente tipos primitivos, daí já não concordo. Se eu tiver um arraylist de String dentro do objeto, ele será serializado também, assim como uma lista contendo qualquer tipo de objeto.
E a BaseEntity é uma classe abstrata que contém basicamente get e set do atributo id.

[]'s
Daniel

ele não deveria serializar listas a menos que vc adicione-a com include()

não tem nenhum customJSONSerialization ou coisa do tipo no projeto?

Criei uma interface e classe, mas o código que postei não utilizam ela.
De qualquer forma, seguem os arquivos:

Interface JsonUtil:

import br.com.caelum.vraptor.serialization.JSONSerialization;

public interface JsonUtil extends JSONSerialization {
	
	public JsonUtilImpl success(final Boolean success);
	
	public JsonUtilImpl redirect(final String url);
	
	public JsonUtilImpl selected(final Object value);
}

Classe JsonUtilImpl:

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.servlet.http.HttpServletResponse;

import br.com.caelum.vraptor.interceptor.TypeNameExtractor;
import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.serialization.ProxyInitializer;
import br.com.caelum.vraptor.serialization.SerializerBuilder;
import br.com.caelum.vraptor.serialization.xstream.XStreamJSONSerialization;
import br.com.caelum.vraptor.serialization.xstream.XStreamSerializer;
import br.com.caelum.vraptor.view.ResultException;

@Component
public class JsonUtilImpl extends XStreamJSONSerialization implements JsonUtil {

	private final HttpServletResponse response;
	private final TypeNameExtractor extractor;
	private final ProxyInitializer initializer;
	
	private final String SUCCESS   = "\"success\": @value, \n";
	private final String REDIRECT  = "\"redirect\": \"@value\", \n";
	private final String SELECTED  = "\"selected\": @value, \n";

	private StringBuilder content;
	
	public JsonUtilImpl(HttpServletResponse response, TypeNameExtractor extractor, ProxyInitializer initializer) {
		super(response, extractor, initializer);
		this.response = response;
		this.extractor = extractor;
		this.initializer = initializer;
	}
	
	public JsonUtilImpl success(final Boolean success) {
		getContent().append(SUCCESS.replace("@value", success.toString()));
		getSerializer();
		return this;
	}
	
	public JsonUtilImpl redirect(final String url) {
		getContent().append(REDIRECT.replace("@value", url));
		getSerializer();
		return this;
	}
	
	public JsonUtilImpl selected(final Object value) {
		if(isNumeric(value.getClass())) {
			getContent().append(SELECTED.replace("@value", value.toString()));
		} else {
			getContent().append(SELECTED.replace("@value", "\"" +  value.toString() + "\""));
		}
		getSerializer();
		return this;
	}
	
	protected SerializerBuilder getSerializer() {
		try {
			final PrintWriter writer = response.getWriter();
			final StringWriter out = new StringWriter();
			return new XStreamSerializer(getXStream(), new PrintWriter(out), extractor, initializer) {
				@Override
				public void serialize() {
					super.serialize();
					writer.append(out.getBuffer().replace(0, 1, content.toString()));
					writer.close();
				}
			};
		} catch (IOException e) {
			throw new ResultException("Unable to serialize data", e);
		}
	}
	
	private StringBuilder getContent() {
		if(this.content == null) {
			this.content = new StringBuilder();
			this.content.append("{");
		}
		return this.content;
	}
	
	private boolean isNumeric(Class clazz) {
		return Integer.class.equals(clazz) 
			|| Double.class.equals(clazz) 
			|| Long.class.equals(clazz)
			|| Number.class.equals(clazz);
	}
	
}

Para essa interface a chamada ficaria assim:

this.result.use(JsonUtil.class).success(true).redirect("/app/secure/home").selected("1").indented().from(usuario).serialize();

Mas o erro é o mesmo.

Então para tratar isso eu criei uma classe Helper:

import java.lang.reflect.Field;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import net.vidageek.mirror.dsl.Mirror;

public class CollectionHelper {
	
	public static List clean(List list) {
		return clean(list, new String[] {});
	}
	
	public static List clean(List list, String ... exclude) {
		int i = 0;
		for(Object entity : list) {
			list.set(i, clean(entity, exclude));
			i++;
		}
		return list;
	}
	
	public static Object clean(Object entity) {
		return clean(entity, new String[] {});
	}
	
	public static Object clean(Object entity, String ... exclude) {
		Mirror mirror = new Mirror();
		for (Field field : new Mirror().on(entity.getClass()).reflectAll().fields()) {
			if (!isPrimitive(field.getType()) && exclude(field, exclude)) {
				mirror.on(entity).set().field(field).withValue(null);
			}
		}
		
		return entity;
	}
	
	private static boolean exclude(Field field, String[] list) {
		for(String param : list) {
			if(field.getName().equals(param)) { 
				return false;
			}
		}
		return true;
	}

	private static boolean isPrimitive(Class<?> type) {
		return type.isPrimitive()
			|| type.isEnum()
			|| Number.class.isAssignableFrom(type)
			|| type.equals(String.class)
			|| Date.class.isAssignableFrom(type)
			|| Calendar.class.isAssignableFrom(type)
			|| Boolean.class.equals(type)
			|| Character.class.equals(type);
	}

}

Chamada:

usuario = (Usuario) CollectionHelper.clean(usuario);

sua classe JsonUtil extends JSONSerialization…

adivinha qual é a classe que o Results.json() retorna :wink:

então toda vez que vc usa json() o VRaptor vai procurar uma implementação de JSONSerialization e usar a sua, a JsonUtilImpl

o estranho é que vc não chama o recursive(), nem nada do tipo, então o VRaptor deveria usar o comportamento padrão. Você consegue reproduzir esse bug no blank-project? Faz isso por favor?

PS: algum motivo pra chamar o getSerializer() em vários métodos?

Viajei, ele não serializa mesmo as listas.
Fiz o seguinte teste e funcionou:

        @Path("/json")
	public void teste() {
		Usuario usuario = new Usuario();
		usuario.setId(1);
		usuario.setChaveUsuario("121324343werrsfd");
		usuario.setUsuarioRegraList(new ArrayList<UsuarioRegra>());
		UsuarioRegra ur = new UsuarioRegra();
		ur.setId(1);
		ur.setUsuario(usuario);
		usuario.getUsuarioRegraList().add(ur);
		this.result.use(JsonUtil.class).success(true).redirect("/app/secure/home").selected("1").indented().from(usuario).include("usuarioRegraList").serialize();
	}

O resultado foi:

{"success": true, 
"redirect": "/app/secure/home", 
"selected": "1", 
"usuario": {
  "id": 1,
  "chaveUsuario": "121324343werrsfd",
  "usuarioRegraList": [
    {
      "id": 1
    }
  ]
}}

Será que a maneira que o JPA preenche o objeto ou o Lazy não são o problema?
Que essas listas vindas do banco ficam como PersistentBag, não é?

Tenho sempre o getSerializer() porque nem sempre faço uso de todos os métodos ao utilizar essa classe.

o ProxyInitializer deveria cuidar desse problema…

debuga e coloca um breakpoint na linha do result.use(json()) e dá um inspect no usuario (ctrl+shift+I)

tira um print e me manda o resultado desse inspect, por favor?

Print

o usuario é um Usuario ou um proxy?

Mas estou utilizando EJB3 no JBoss 6, então no controller já não existe mais sessão do JPA para fazer o load.

ele deveria ignorar a lista por padrão, algo tá fazendo com que ela serialize.

precisava tentar isolar esse bug mesmo…

se vc tirar o @Component do seu JsonUtilImpl e usar result.use(json())… funciona?

Retirei como você pediu, mas o erro persiste.
Para confiar limpei o usuario assim:

usuario = (Usuario) CollectionHelper.clean(usuario);
this.result.use(json()).indented().from(usuario).exclude("usuarioRegraList").serialize();

Dessa forma a serialização ocorreu corretamente.

E para garantir limpei e passando como parâmetro a lista usuarioRegraList, para garantir que qualquer outra coisa fosse removida.

usuario = (Usuario) CollectionHelper.clean(usuario,  "usuarioRegraList");
this.result.use(json()).indented().from(usuario).exclude("usuarioRegraList").serialize();

Mas dessa forma o erro persistiu.

Eu ainda acredito que o XStream não enxergue o PersistentBag como coleção, e sim como um objeto. E por isso que ele entra na referência cirular.
Tanto que, se a lista for populada manualmente, como no exemplo que postei acima, o erro não ocorre.

precisava tentar isolar o problema mesmo. Não consigo ver motivos pra essa circular reference…

consegue reproduzir isso no blank-project?

Bom, sono foi embora mesmo…vou tentar fazer isso agora.

blz, valeu =)

Criei um projeto em branco, criei uma base com as tabelas usuario, regra e usuario_regra e fiz um findById via JPA para testar a serialização.
Funcionou para o seguinte código:

Usuario usuario = new Usuario();
usuario.setId(1);
usuario = dao.selectById(usuario);
result.use(json()).indented().from(usuario).serialize();

Já se eu adicionar a coleção via include o erro de CircularReference aparece:

Usuario usuario = new Usuario();
usuario.setId(1);
usuario = dao.selectById(usuario);
result.use(json()).indented().from(usuario).include("usuarioRegraList").serialize();

Então deve ser alguma coisa nas minhas classes mesmo.
Amanhã continuo os testes.

Projeto que fiz os testes pode ser baixado aqui: http://k2cloud.net/vraptor-teste.zip
*Projeto p/ Eclipse e JBoss 6