Wednesday, September 15, 2010

Web Service - A cycle is detected in the object graph. This will cause infinitely deep XML...

When a Web Service that uses an EJB that exposes an Entity Class and this Entity Class makes relationships with another Entity Class, may be you will receives this error:

Caused by: javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: org.test.data.Business[id=1] -> org.test.data.BusinessType[id=1] -> org.test.data.Business[id=1]]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:269)
at com.sun.xml.bind.v2.runtime.BridgeImpl.marshal(BridgeImpl.java:100)
at com.sun.xml.bind.api.Bridge.marshal(Bridge.java:141)
at com.sun.xml.ws.message.jaxb.JAXBMessage.writePayloadTo(JAXBMessage.java:317)
... 35 more
Caused by: com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: org.test.data.Business[id=1] -> org.test.data.BusinessType[id=1] -> org.test.data.Business[id=1]
at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:248)
at com.sun.xml.bind.v2.runtime.XMLSerializer.pushObject(XMLSerializer.java:537)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:631)
at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:155)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:340)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:152)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:340)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:155)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:340)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:264)
... 38 more



To this error above you can found solutions saying to use this interface:
  • com.sun.xml.internal.bind.CycleRecoverable


NetBeans recognize this package and not mark like an error. But on compile time this package does not exist to compiler side. Perhaps you will receive this error when compiles:


/home/eduveks/project/wstest/src/org/test/data/Business.java:8: package com.sun.xml.internal.bind does not exist
import com.sun.xml.internal.bind.CycleRecoverable;


On my view this error is a bug of NetBeans that recognizes this package when isn't a valid package to Java compiler. To test this bug you only need put this "import com.sun.xml.internal.bind.CycleRecoverable;" into any Java file and you will see that NetBeans don't says nothing and accept as well, but if you "Clean and Build" the error will show up.

The correct package is (without .internal):


import com.sun.xml.bind.CycleRecoverable;


To uses that package path above you need add it:

Project Properties -> Libraries -> "Add Library...", select JAXB 2.2 and "Add Library".

A sample how to implements the com.sun.xml.bind.CycleRecoverable:

    > Business.java

package org.test.data;

import com.sun.xml.bind.CycleRecoverable;
import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.test.data.adapter.IntegerAdapter;

@Entity
@Table(name = "Business")
@NamedQueries({
@NamedQuery(name = "Business.findAll", query = "SELECT b FROM Business b"),
@NamedQuery(name = "Business.findById", query = "SELECT b FROM Business b WHERE b.id = :id"),
@NamedQuery(name = "Business.findByNome", query = "SELECT b FROM Business b WHERE b.name = :name"),
public class Business implements Serializable, CycleRecoverable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "Id")
private Integer id;
@Basic(optional = false)
@Column(name = "Name")
private String name;
@JoinColumn(name = "BusinessTypeId", referencedColumnName = "Id")
@ManyToOne(optional = false)
private BusinessType businessType;

public Business() {
}

public Business(Integer id) {
this.id = id;
}

public Business(Integer id, String name) {
this.id = id;
this.name = name;
}

@XmlID
@XmlJavaTypeAdapter(value = IntegerAdapter.class, type = String.class)
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@XmlIDREF
public BusinessType getBusinessType() {
return businessType;
}

public void setBusinessType(BusinessType businessType) {
this.businessType = businessType;
}

@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}

@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Business)) {
return false;
}
Business other = (Business) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}

@Override
public String toString() {
return "org.test.data.Business[id=" + id + "]";
}

@Override
public Object onCycleDetected(Context cntxt) {
System.out.println("CycleRecoverable.onCycleDetected # ".concat(this.toString()));
Business n = new Business();
n.setId(this.getId());
return n;
}
}


    > BusinessType.java

package org.test.data;

import com.sun.xml.bind.CycleRecoverable;
import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.test.data.adapter.IntegerAdapter;

@Entity
@Table(name = "BusinessType")
@NamedQueries({
@NamedQuery(name = "BusinessType.findAll", query = "SELECT bt FROM BusinessType bt"),
@NamedQuery(name = "BusinessType.findById", query = "SELECT bt FROM BusinessType bt WHERE bt.id = :id"),
@NamedQuery(name = "BusinessType.findByName", query = "SELECT bt FROM BusinessType bt WHERE bt.name = :name")})
public class BusinessType implements Serializable, CycleRecoverable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "Id")
private Integer id;
@Basic(optional = false)
@Column(name = "Name")
private String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "BusinessType")
private Collection businessCollection;

public BusinessType() {
}

public BusinessType(Integer id) {
this.id = id;
}

public BusinessType(Integer id, String name) {
this.id = id;
this.name = name;
}

@XmlID
@XmlJavaTypeAdapter(value = IntegerAdapter.class, type = String.class)
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@XmlIDREF
public Collection getBusinessCollection() {
return businessCollection;
}

public void setBusinessCollection(Collection businessCollection) {
this.businessCollection = businessCollection;
}

@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}

@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof BusinessType)) {
return false;
}
BusinessType other = (BusinessType) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}

@Override
public String toString() {
return "org.test.data.BusinessType[id=" + id + "]";
}

@Override
public Object onCycleDetected(Context cntxt) {
System.out.println("CycleRecoverable.onCycleDetected # ".concat(this.toString()));
BusinessType n = new BusinessType();
n.setId(this.getId());
return n;
}
}


You need mark the IDs with @XmlID(only before the get method, before the variable declaration don't works):

@XmlID
@XmlJavaTypeAdapter(value = IntegerAdapter.class, type = String.class)
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}


And mark all Collection relationships methods with @XmlIDREF(only before the get method, before the variable declaration don't works):

    > BusinessType.java

@XmlIDREF
public Collection getBusinessCollection() {
return businessCollection;
}

public void setBusinessCollection(Collection businessCollection) {
this.businessCollection = businessCollection;
}


Sample of the IntegerAdapter.java:

package org.test.data.adapter;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class IntegerAdapter extends XmlAdapter {

@Override
public Integer unmarshal(String v) {
return Integer.parseInt(v);
}

@Override
public String marshal(Integer v) {
return v.toString();
}
}



Thursday, July 29, 2010

Automate Command-line Programs

I had to automate a command-line program. Then I did this script like a sample that do auto control to vi.

This way is possible control any kind of command-line program.

#!/bin/python

import subprocess
import time
import threading

class ProcessRunner(threading.Thread):
def run(self):
self.process = subprocess.Popen(["vi /tmp/xpto > /tmp/viOutput"], shell = True, bufsize = 1000, stdout = subprocess.PIPE, stdin = subprocess.PIPE, stderr = subprocess.STDOUT)

pr = ProcessRunner()
pr.start()

time.sleep(1)

pr.process.stdin.write("i\n")
pr.process.stdin.flush()

time.sleep(1)

pr.process.stdin.write("Content xpto... :P\n")
pr.process.stdin.flush()

time.sleep(1)

pr.process.stdin.write(chr(27))
pr.process.stdin.flush()

time.sleep(1)

pr.process.stdin.write(":wq\n")
pr.process.stdin.flush()


If is necessary read some content from program output, you can put the output in a temp file, in this case goes to /tmp/viOutput. See with attention the line of the subprocess.Popen. Another way is use the pr.process.stdout but it dont work fine for me.

The command pr.process.stdin.write(chr(27)) write the "character" ESC, like the keyboard key.

Thursday, February 4, 2010

NC (NetCast) vs Printers (Impressoras)

Sobrou para mim configurar as novas impressoras Samsung SCX-6345N no Solaris, e como é óbvio e uma grande pena não tem drivers, ou pelo menos que funcione bem, pois apesar de haver um driver PPD supostamente para o LP em Solaris, não funcionou nem a pau.

Fiz tudo o possível para fazer funcionar estas impressoras em Solaris via LP, pois era uma situação de urgência fazer o sistema da Reuters (Kondor) utilizar as novas impressoras.

Após muitas tentativas e alguns e-mails trocados com o suporte da Samsung, e até um programa da Samsung para instalar a impressora em modo gráfico (X11) que "configuraria automaticamente" o LP, também não funcionou. E após também de muitas folhas de erros e cabeçalho de jobs terem sido impressos sem nunca sair o conteúdo desejado. Após tudo isto e um pouco mais... o suporte da Samsung recomendou eu usar o comando:

# cat testprint.ps | nc -w 2 192.168.193.191 9100


Fiquei sético, mas como não havia muito mais a fazer, lá fui eu experimentar isto, e para a minha surpresa... não é que funcionou!

Por acaso eu já tinha pensado nisto, em abrir uma conexão com a impressora direto e mandar o conteúdo PostScript e ver se ela imprimia ou se ficava maluca. Mas não sabia que existia um comando simples para isto.

O comando NC (NetCast) não vem no Solaris 10, por isso fui ao Sunfreeware.com pegar o pacote:

http://www.sunfreeware.com/programlistsparc10.html#nc

Depois é só instalar o pacote:

#pkgadd -d nc-110-sol10-sparc-local.gz


Na configuração da impressora no Kondor (Reuters) ficou apenas assim:

nc -w 2 192.168.193.191 9100


Portanto é executar o nc passando o IP e a PORTA da impressora. O -w 2 apenas quer dizer que tem o timeout de 2 segundos.

A desvantagem desta solução é que perde-se o controle das impressoras com o LP, ou seja, para o Solaris é como se elas não existissem. Então a administração LP e a configuração das impressoras no /etc/lp e os comandos ("lpstat -a", "enable PRINTER" e "disable PRINTER"), tudo isto passa a servir para nada! :D

Como para nos a solução do NC (NetCast) é suficiente, passamos a trabalhar assim.

NC (NetCast)

netcat is a simple unix utility which reads and writes data across network connections, using TCP or UDP protocol. It is designed to be a reliable "back-end" tool that can be used directly or easily driven by other programs and scripts. At the same time, it is a feature-rich network debugging and exploration tool, since it can create almost any kind of connection you would need and has several interesting built-in capabilities. Netcat, or "nc" as the actual program is named, should have been supplied long ago as another one of those cryptic but standard Unix tools.

http://www.computerhope.com/unix/nc.htm

Esta solução de imprimir diretamente para uma impressora de rede usando o NC (NetCast) também é válida para Linux e Unix em geral.

Tuesday, October 20, 2009

Solaris - PDF Printer

Precisei encarar este desafio de configurar uma impressora de PDF em Solaris, para receber a impressão de documentos em PostScript apartir do Kondor.

Encontrei este link que ajudou bastante:

http://aplawrence.com/SCOFAQ/FAQ_scotec7printtofile.html

Então os passos que fiz e cheguei ao sucesso são...

Criar a pasta que irá conter o script que receberá a os dados de impressão:

# mkdir /usr/local/PDFprinter


Criar uma outra pasta que irá conter o arquivos de PDF:

# mkdir /var/PDFprinter


Gerar este arquivo de script em /usr/local/PDFprinter/start que será usado para preparar o device da impressora e manter o device em sincronização com o script de impressão:

mknod /dev/PDFprinter p
chmod 777 /dev/PDFprinter
while true
do
cat /dev/PDFprinter | /usr/local/PDFprinter/print
done


Permissões de execussão:

# chmod +x /usr/local/PDFprinter/start


Gerar o script de impressão em /usr/local/PDFprinter/print:

date=`date "+%G%m%d_%H%M%S"`
tempFileName="/tmp/PDFprinter_$date"
while read stuff
do
echo $stuff >> $tempFileName
done
cat $tempFileName | grep -v "#####" > $tempFileName.tmp
mv $tempFileName.tmp $tempFileName
fileName="/var/PDFprinter/PDFprinter_$date.pdf"
ps2pdf -sPAPERSIZE=a4 $tempFileName $fileName


O comando ps2pdf faz parte do pacote ghostscript:

http://www.sunfreeware.com/programlistsparc10.html#ghostscript

E para definir o tamanho do papel é com o parâmetro ps2pdf -sPAPERSIZE=a4 ...

Permissões de execussão:

# chmod +x /usr/local/PDFprinter/print


Preparar e iniciar o device da impressora:

# /usr/local/PDFprinter/start &


Registrar o device da impressora e activar a impressora:

# lpadmin -p PDFPrinter -v /dev/PDFprinter
# accept PDFPrinter
# enable PDFPrinter


Agora é só mandar alguma coisa em PostScript para imprimir nesta impressora e ver o resultado em arquivos temporários em /tmp, e caso gere o PDF com sucesso deverá estar em /var/PDFprinter;

Monday, September 28, 2009

Process ID

Quando se faz um serviços Unix em Java para poder fazer os scripts de stop, kill, etc, é preciso o PID (número do processo), então para tal é preciso gerar um arquivo que contém esta informação.

Portanto este código faz exatamente isto, gera um arquivo com o PID da aplicação Java:

java.io.BufferedWriter bw = null;
try {
bw = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream("my.pid")));
String pidInfo = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
bw.write(pidInfo.substring(0, pidInfo.indexOf('@')));
} finally {
if (bw != null) bw.close();
}

Friday, June 19, 2009

Jetty e Web Services com JAX-WS

O Jetty é um poderoso servidor web, disto não há dúvidas, mas no que toca a web services paira uma nuvem negra, não há muita documentação e nem integração com IDEs.

Depois de pesquisar um pouco vi que era possível integrar com o JAX-WS.

Não encontrei nenhuma explicação detalhada de como fazer, e que explicasse também a parte do cliente, então resolvi fazer o download do JAX-WS e ver os exemplos e a documentação, e a conclusão que cheguei segue abaixo.

Levando em consideração que já tem o Jetty configurado e rodando, no meu caso foi a versão 6 e com o JDK 6.

Fazer o download do JAX-WS no meu caso foi a versão 2.1.7 em:

https://jax-ws.dev.java.net/

Descompactar e em jaxws-ri/docs/UsersGuide.html#3.1_Server da para ver alguma explicação de como fazer.

Copiar todos os jars que estão na pasta lib do JAX-WS para a pasta lib/ext do Jetty.

Agora no seu projeto web criar as seguintes classes de exemplo (também estão em jaxws-ri/samples/fromjava/src), aqui dei alguma melhoria nas anotações:

-> fromjava/server/AddNumbersImpl.java:

package fromjava.server;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import javax.annotation.Resource;
@WebService
public class AddNumbersImpl {
@Resource
private WebServiceContext context;
@WebMethod(operationName = "add")
public int addNumbers(@WebParam(name = "first") int number1, @WebParam(name = "second") int number2) throws AddNumbersException {
// SERVLET - IF NEED SERVLET RESOURCES. For example to do session control.
MessageContext messageContext = context.getMessageContext();
ServletContext servletContext = (ServletContext)messageContext.get(MessageContext.SERVLET_CONTEXT);
HttpServletRequest servletRequest = (HttpServletRequest)messageContext.get(MessageContext.SERVLET_REQUEST);
HttpServletResponse servletResponse = (HttpServletResponse)messageContext.get(MessageContext.SERVLET_RESPONSE);
// --
if (number1 < 0 || number2 < 0) {
throw new AddNumbersException("Negative number cant be added!",
"Numbers: " + number1 + ", " + number2);
}
return number1 + number2;
}
}

-> fromjava/server/AddNumbersException.java:

package fromjava.server;
public class AddNumbersException extends Exception {
String detail;
public AddNumbersException (String message, String detail) {
super (message);
this.detail = detail;
}
public String getDetail () {
return detail;
}
}

Acrescentar no web.xml:

<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
<description>JAX-WS endpoint - fromjava</description>
<display-name>fromjava</display-name>
<servlet-name>fromjava</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fromjava</servlet-name>
<url-pattern>/addnumbers</url-pattern>
</servlet-mapping>

E junto com o web.xml criar o arquivo sun-jaxws.xml:

<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
<endpoint
name='fromjava'
implementation='fromjava.server.AddNumbersImpl'
url-pattern='/addnumbers'/>
</endpoints>

Feito isto, é rodar o Jetty e ver o WSDL:

http://localhost:8080/WSTester/addnumbers?WSDL

Onde o WSTester é o nome do seu projeto.

Agora temos que criar as classes para chamar o web service, para isto o JAX-WS trás o bin/wsimport, no meu caso como estou no Linux vou usar o .sh, caso esteja no Windows é só usar o .bat, criar as pastas src e dest dentro da pasta do JAX-WS.

$ chmod +x bin/wsimport.sh
$ mkdir dest
$ mkdir src
$ bin/wsimport.sh -d dest -s src http://localhost:8080/WSTester/addnumbers?WSDL

O wsimport vai gerar o código das classes para invocar o web service na pasta src, e as classes compiladas em dest.

Eu peguei na pasta src/fromjava e copiei para o um outro projeto de aplicação do tipo consola em Java. Assim pode chamar o web service:

TestAddNumbers.java:

package javaapplicationWSTester;
import fromjava.server.*;
public class TestAddNumbers {
public static void main(String[] args) throws AddNumbersException_Exception {
int result = new AddNumbersImplService().getAddNumbersImplPort().add(3, 5);
System.out.println(result);
}
}

O processo é simples, ter a classe do web service, configurar no web.xml e no sun-jaxws.xml, gerar as classes de invocação com o wsimport e boa :P

Monday, May 18, 2009

SUDO + PHP - Executando comandos como Root no PHP

É preciso saber qual é o usuário de execução do PHP:

<?php echo exec("whoami"); ?>


Editar o /etc/sudoers, este arquivo tem que ter permissões apenas de leitura, não pode ficar com permissões de escrita, por isso antes de editar o arquivo dê permissões de escrita:

# chmod u+w /etc/sudoers


Depois de editar, tire a permissão de escrita:

# chmod u-w /etc/sudoers


Então adicionar no /etc/sudoers no fim do arquivo:

%USUARIO_DO_PHP ALL= NOPASSWD: /CAMINHO/DO/COMANDO


Se não colocar no fim do arquivo pode ter outra regra depois que anule esta nova regra.

No PHP é só executar o comando com o SUDO:

<?php echo exec("sudo /CAMINHO/DO/COMANDO.sh"); ?>


Caso não tenha sucesso, verifique os logs de segurança:

# tail -n 100 /var/log/secure


Se tiver esta mensagem de erro:

May 18 16:56:41 SERVIDOR sudo: USUARIO_DO_PHP : sorry, you must have a tty to run sudo ; TTY=unknown ; PWD=/PASTA/DO/PHP ; USER=root ; COMMAND=/CAMINHO/DO/COMANDO


Então é preciso ir no /etc/sudoers e comentar a seguinte linha que contém o "Defaults requiretty", assim:

# Defaults requiretty


Assim deverá funcionar.