SOAP-Fehlermeldung WebServiceException: Could not send Message umgehen

Beim Verbindungsaufbau zu einem WebService, bin ich über ein Problem gestolpert, dass der Zugriff auf den WebService nicht mehr möglich war. Obwohl bei der Generierung des Services (javax.xml.ws.Service) die URL zur WSDL-Datei angegeben wurde und in dieser Datei die Endpoint-URL definiert war, funktionierte der Aufruf jeweils nur einmal nach dem Deployment im jBoss Application Server. Warum? Keine Ahnung. Zunächst wurde die Verbindung wie folgt aufgebaut:

String plainUrl = "http://mywebserivce:9999/call.php?wsdl";
Url url = new Url(plainUrl);
PrintManagerService service = new PrintManagerService(url);	
soap = service.getPrintManagerPort();

In der lokalen Testumgebung (Windows) funktionierte das einwandfrei. Erst im Produktionssystem (Linux; vermutlich spielt das BS hier keine Rolle) kam es zu folgender Fehlermeldung:


javax.xml.ws.WebServiceException: Could not send Message.
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:135)
...
Caused by: java.io.IOException: IOException invoking http://localhost:8080/call.php: HTTP response '404: Not Found'

Der Host wurde offensichtlich von mywebservice:9999 auf localhost:8080 geändert. Im Code konnte ich keine Stelle finden, an dem vom localhost auf Port 8080 die Rede war. Durch Ergänzung von

((BindingProvider)soap).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, plainUrl);

konnte das Problem gelöst werden. Nun wird einmal die URL übergeben und direkt danach der Endpoint ein weiteres Mal gesetzt:

String plainUrl = "http://mywebserivce:9999/call.php?wsdl";
Url url = new Url(plainUrl);
PrintManagerService service = new PrintManagerService(url);	
soap = service.getPrintManagerPort();
((BindingProvider)soap).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, plainUrl);

Verbindungsaufbau zur Plentymarkets SOAP Api Version 110 mit Java

Das folgende Beispiel zeigt die Verwendung der Plentymarkets SOAP Api Version 110. Die SOAP-Klassen wurden mittels dem in Eclipse eingebauten Tool zur Erstellung von Java-Klassen anhand einer WSDL-Datei generiert.

public class PlentyConnector {
	private Logger logger = Logger.getLogger(PlentyConnector.class);
	private String TOKEN = "";
	private int USERID = -1;
	private PlentySoapApiFunctionContainerServiceStub service = null;

// die weiteren Methoden werden separat gezeigt

}

In der folgenden Methoden wird ein Token geladen, sofern dies noch nicht geschehen ist.

   private void createService(String accountUser, String accountPassword, String portAddress) throws Exception {
		try {
			service = new PlentySoapApiFunctionContainerServiceStub(portAddress);
			if (this.TOKEN.equals("") || USERID == -1) {
                                //request a new token
				GetAuthentificationToken req = new GetAuthentificationToken();
				req.setOLogin(new PlentySoapRequest_GetAuthentificationToken());
				req.getOLogin().setUsername(accountUser);
				req.getOLogin().setUserpass(accountPassword);
				GetAuthentificationTokenResponse resp = service.getAuthentificationToken(req);
				this.TOKEN = resp.get_return().getToken();
				this.USERID = resp.get_return().getUserID();
			}
                        //modify the service and set the userid and token as a header element
			SOAPFactory factory = OMAbstractFactory.getSOAP12Factory();
		    SOAPHeaderBlock header = factory.createSOAPHeaderBlock("verifyingToken", null);
		    OMFactory omFactory = OMAbstractFactory.getOMFactory();
		    OMNode userNameNode = omFactory.createOMElement(new QName("UserID"));
		    ((OMElement) userNameNode).setText(""+USERID);
		    header.addChild(userNameNode);
		    OMNode passwordNode = omFactory.createOMElement(new QName("Token"));
		    ((OMElement) passwordNode).setText(TOKEN);
		    header.addChild(passwordNode);
		    service._getServiceClient().addHeader(header);
			System.out.println("token: " + this.TOKEN + " userid: " + this.USERID);
		}
		catch(Exception e) {
			logger.error("Authentification token not loaded correctly!");
			throw e;
		}
	}

Die nächste Methode zeigt am Beispiel der Serverzeit, wie weitere Aufrufe erfolgen können:

	private void getServerTime() {
		try {
	    	PlentySoapResponse_GetServerTime resp = service.getServerTime(new GetServerTime()).get_return();
	    	System.out.println("Servertime : "+resp.getTimestamp());
	   }   
	   catch(Exception e) {
		   logger.error("Server time not loaded correctly: " + e.getMessage(), e);
	   }
	}

In der nachfolgenden Methode wird ein Aufruf mit ein paar Testdaten durchgeführt, um die Funktionsweise zu veranschaulichen:

	public void executePlentyCall() throws Exception {
		System.out.println("login");
		createService("MyUserName", "MyPassword", "http://my.plenty.installation.de/soap/version110/");
		getServerTime();
}

Die SOAP-Klassen zum Verbindungsaufbau wurden mit Hilfe des axis2-Tools wsdl2java.bat erstellt.

REST-Webservice mit PHP ansprechen

REST-Webservice mit PHP ansprechen

In diesem Beitrag geht es um das Ansprechen eines REST-Webservices mittels PHP, wobei der REST-Webservice die Ergebnisse mit Hilfe von JSON zurückliefert. REST steht für Representational State Transfer und stellt eine Möglichkeit zur Implementierung einer Schnittstelle dar. Rückgaben können auf verschiedene Arten erfolgen, z.B. durch XML oder JSON. Das Ansprechen eines SOAP-Webservices ist mit Hilfe der in PHP eingebauten Soap-Klasse möglich (SoapClient). Für das Arbeiten mit einem REST-Webservice kann beispielsweise auf cURL zurückgegriffen werden, was auch Inhalt dieses Beitrags sein wird.

Methode zum Aufruf des Services

public function callAPI($data = false)
{
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_POST, 1);
	curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
	curl_setopt($curl, CURLOPT_URL, $this->serviceUrl);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
	curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
	$ret = curl_exec($curl);
	if ($ret === false) {
		$ret = null;
	}
	curl_close($curl);
	return $ret;
}

Zunächst wird mit curl_init(); eine neue cURL-Instanz erzeugt. Post-Daten können eingesetzt werden (curl_setopt($curl, CURLOPT_POSTFIELDS, $data);) und die URL zum Webservice wird gesetzt (curl_setopt($curl, CURLOPT_URL, $this->serviceUrl);). Anschließend wird der Service aufgerufen und der Rückgabewert zurückgegeben ($ret = curl_exec($curl);). Wichtig ist, dass vor der Rückgabe die cURL-Verbindung geschlossen wird (curl_close($curl);).

Methode zum Auslesen der JSON-Rückgabe

Angenommen, in der Rückgabe stecken Benutzerinformationen als JSON-Objekt, kann das Auslesen mit folgende Methode vorgenommen werden:

public function getUserEntityFromInfo($userInfo) {
	$obj = json_decode($userInfo);
	if ($obj && isset($obj->{'username'})) {
		$user = new UserEntity();
		$user->setuserName($obj->{'username'});
		$user->setemail($obj->{'useremail'});
		return $user;
	}
	return null;
}

Dabei wird zunächst das JSON-Objekt anhand des Strings erzeugt ($obj = json_decode($userInfo);). Beinhaltet das Objekt username, so werden username und useremail ausgelesen. Die Klasse UserEntity besteht in diesem Beispiel lediglich aus Getter- und Setter-Methoden.

Die komplette Klasse

class RestExample {
	
	private $serviceUrl = "https://www.myservice.com/servicename";
	
	public function callAPI($data = false)
	{
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($curl, CURLOPT_URL, $this->serviceUrl);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
		$ret = curl_exec($curl);
		if ($ret === false) {
			$ret = null;
		}
		curl_close($curl);
		return $ret;
	}
	
	public function getUserEntityFromInfo($userInfo) {
		$obj = json_decode($userInfo);
		if ($obj && isset($obj->{'username'})) {
			$user = new UserEntity();
			$user->setuserName($obj->{'username'});
			$user->setemail($obj->{'useremail'});
			return $user;
		}
		return null;
	}
}

Aufruf der Klasse

Das folgende Beispiel zeigt, wie diese Klasse aufgerufen und Post-Werte übergeben werden können.

$restClient = new RestExample();
$curlPostData = array(
		'MyFirstProperty' => 123,
		'MySecondEntry' => "testdata"
);
$ret = $restClient->callAPI($curlPostData);
if ($ret != null) {
	$user = $restClient->getUserEntityFromInfo($ret);
	if ($user != null) {
		echo $user->getuserName();
	}
	else {
		echo "Benutzerinformationen konnten nicht geladen werden.";
	}
}
else {
	echo "Keine Rückgabe nach Webservice-Aufruf erhalten.";
}

Chrome-Extension zum Testen eines REST-Webservices

Zum Schnellen Testen eines REST-Webservices, bietet sich die Google Chrome-Extension Advance REST Client an. Mit dieser Extension können sowohl GET- als auch POST-Requests getestet werden und das Übergeben von Variablen ist möglich.

Anmerkungen

Die hier gezeigten Beispiele sind bewusst recht simpel gehalten. Das Aufrufen eines REST-Webservices wird gezeigt und es ist ersichtlich, wie POST-Daten übergeben werden können. Auch das Erzeugen eines JSON-Objektes wird gezeigt.

MysqlDataTruncation: Data truncation: Data too long for column

Beim Speichern eines neuen Datensatzes in einem Java-Webservice, bin ich auf den Fehler „Data too long for column“ gestoßen. Eigentlich eine klare Sache – doch nicht in diesem Fall.

Die Entwicklungsumgebung:

  • jboss application server
  • Java 1.7, JPA und Hibernate
  • MySQL-Datenbank

Die Java-Bean hat ein Feld namens myclass vom Typen MyClass (Klassennamen wurde geändert ;-)). Das sieht ungefähr so aus:

@Column(nullable=false);
private MyClass myclass;

Das Bauen der War-Datei und das Deployen funktioniert reibungslos. Auch können andere Datensätze problemlos eingetragen werden. Doch sobald oben erwähnter Datensatz mittels em.persist in Die Datenbank übernommen werden soll, erscheint eine recht lange Fehlermeldung. Ein Auszug der Fehlermeldung:

Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'myclass' at row 1
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4188) [mysql-connector-java-5.1.20.jar:]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4122) [mysql-connector-java-5.1.20.jar:]
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2570) [mysql-connector-java-5.1.20.jar:]
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2731) [mysql-connector-java-5.1.20.jar:]
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2818) [mysql-connector-java-5.1.20.jar:]
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2157) [mysql-connector-java-5.1.20.ja
r:]
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2460) [mysql-connector-java-5.1.20.jar:
]
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2377) [mysql-connector-java-5.1.20.jar:
]
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2361) [mysql-connector-java-5.1.20.jar:
]
at org.jboss.jca.adapters.jdbc.WrappedPreparedStatement.executeUpdate(WrappedPreparedStatement.java:493)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:133) [hibernate
-core-4.2.0.Final-redhat-1.jar:4.2.0.Final-redhat-1]

Ein Blick in die Datenbank zeigt, dass die Spalte als tinyblob angelegt wurde. Offensichtlich weiß JPA also nicht genau, was ich in dieser Spalte speichern möchte und nimmt an, dass es sich um einen größeren Wert handeln könnte. Daher muss die Spalte etwas genauer spezifiziert werden:

@OneToOne(cascade={CascadeType.REFRESH,CascadeType.MERGE},fetch = FetchType.EAGER)
@JoinColumn(nullable=false)
private MyClass myclass;

Aus @Column wurd ein @JoinColumn und die One-To-One-Beziehung wurde explizit hinzugefügt. Beim Erstellen der Datenbank-Tabelle ist aus dem Feld ein INT mit der Länge 11 geworden und das Hinzufügen des Datensatzes funktioniert wie erwartet.