Czas na wielkie łał, czyli wywołanie usługi… w końcu.

Adapter interfejsu Ideone

I szerzej dowolnej usługi kompilatora. Po co? Oczywiście po to by w razie czego uniezależnić się od dostawcy. Proste.

Listing 1. CompilerApi

package pl.koziolekweb.vaadin.codecompiler.api;

import java.text.ParseException;

import pl.koziolekweb.vaadin.codecompiler.data.TestResponse;

public interface CompilerApi {

	TestResponse testFunction() throws ParseException;

}

Na chwilę obecną wersja uproszczona zawierająca tylko jedną metodę. Klasa TestResponse:

Listing 2. TestResponse

package pl.koziolekweb.vaadin.codecompiler.data;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@Entity
@NamedQueries(value = { @NamedQuery(name = "getAll", query = "Select t from TestResponse t"),
		@NamedQuery(name = "getbyId", query = "Select t from TestResponse t Where t.timestamp=:id") })
public class TestResponse {

	private String moreHelp;
	private String error;
	private Double pi;
	private Boolean oOok;
	private Long answerToLifeAndEverything;
	@Id
	private Timestamp timestamp;

	public Long getAnswerToLifeAndEverything() {
		return answerToLifeAndEverything;
	}

	public String getError() {
		return error;
	}

	public String getMoreHelp() {
		return moreHelp;
	}

	public Boolean getoOok() {
		return oOok;
	}

	public Double getPi() {
		return pi;
	}

	public Timestamp getTimestamp() {
		return timestamp;
	}

	public void setAnswerToLifeAndEverything(Long answerToLifeAndEverything) {
		this.answerToLifeAndEverything = answerToLifeAndEverything;
	}

	public void setError(String error) {
		this.error = error;
	}

	public void setMoreHelp(String moreHelp) {
		this.moreHelp = moreHelp;
	}

	public void setoOok(Boolean oOok) {
		this.oOok = oOok;
	}

	public void setPi(Double pi) {
		this.pi = pi;
	}

	public void setTimestamp(Timestamp timestamp) {
		this.timestamp = timestamp;
	}

}

Reprezentuje odpowiedź serwera wzbogaconą o znacznik czasu, który robi za klucz główny dla encji w bazie danych (nie rób tak w produkcji). Podpięcie do bazy będzie tymczasowe i będzie służyło do momentu kiedy funkcja ta będzie pełniła już tylko rolę testową, a nie wspomagającą deweloperkę.
Metoda testFunction ma tą zaletę, że zawsze zwraca to samo. Zatem idealnie nadaje się do testów. Sama implementacja interfejsu usługi jest prosta jak konstrukcja cepa:

Listing 3. IdeoneCodeCompilerService

package pl.koziolekweb.vaadin.codecompiler.api;

import java.rmi.RemoteException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;

import pl.koziolekweb.vaadin.codecompiler.api.parsers.APIResponseParser;
import pl.koziolekweb.vaadin.codecompiler.com.ideone.api.IdeoneServicePort;
import pl.koziolekweb.vaadin.codecompiler.data.ServiceUser;
import pl.koziolekweb.vaadin.codecompiler.data.TestResponse;
import pl.koziolekweb.vaadin.codecompiler.data.dao.TestResponseDao;

public class IdeoneCodeCompilerService implements CompilerApi {

	private ServiceUser serviceUser;

	private IdeoneServicePort servicePort;

	private APIResponseParser responseParser;

	private TestResponseDao testResponseDao;

	public void setServicePort(IdeoneServicePort servicePort) {
		this.servicePort = servicePort;
	}

	public void setServiceUser(ServiceUser serviceUser) {
		this.serviceUser = serviceUser;
	}


	public void setTestResponseDao(TestResponseDao testResponseDao) {
		this.testResponseDao = testResponseDao;
	}

	@Override
	public TestResponse testFunction() throws ParseException {
		Object[] testFunction;
		TestResponse testResponse;
		try {
			testFunction = servicePort.testFunction(serviceUser.getUser(), serviceUser.getPass());
			testResponse = responseParser.parseTestResponse(testFunction);
		} catch (RemoteException e) {
			testResponse = buildFailedTestResponse(e);
		}
		testResponseDao.create(testResponse);
		return testResponse;
	}

	private TestResponse buildFailedTestResponse(RemoteException e) {
		TestResponse testResponse;
		testResponse = new TestResponse();
		testResponse.setTimestamp(new Timestamp(new Date().getTime()));
		testResponse.setError(e.getLocalizedMessage());
		return testResponse;
	}
}

Składa się ona z kilku rzeczy. Po pierwsze interfejs właściwy usługi. Ten sam, który wygenerowaliśmy za pomocą Axisa (po drobnych zmianach kosmetycznych związanych z mało przyjaznymi nazwami). Mamy też klasę ServiceUser, która trzyma informacje dostępowe. Po drugie APIResponseParser, czyli takie coś, co będzie nam zamieniało odpowiedź serwera na obiekt bliższy javowej estetyce. Niestety Ideone poszedł po najmniejszej linii oporu i zwraca obiekty soap:Array, czyli w praktyce ciąg wartości oddzielonych przecinkami. Naszym zadaniem jest to przerobić na obiekty javy. Jak? O tym później. Po trzecie mamy jeszcze dao, które obsługuje nam zapis do bazy danych. Teraz pytanie czy takie coś nie łamie zasady SRP?

SRP ole!

O SRP pisałem dawno temu… i muszę jeszcze napisać ostatnią część cyklu… no fajnie…

Pisałem wtedy, że SRP nie oznacza jednej czynności, ale jedną odpowiedzialność na pewnym poziomie abstrakcji. Od naszego serwisu wymagamy by udostępniał nam pewne metody, ale też chcemy by była możliwość uzyskania informacji o tym jak zakończyły się wywołania. Informacje te składujemy w bazie. Problem w tym, że nie można wskazać jednoznacznie kto powinien zapisywać te informacje. Czy API, które wywołujemy czy też nasz kod wywołujący? No właśnie… jedną z zalet wprowadzenia interfejsu pośredniego jest możliwość zamknięcia w nim wszystkich tych problematycznych rzeczy. W razie czego wymienimy implementację.
Z punktu widzenia klienta wywołujemy usługę i wiemy, że wynik będzie zawsze do pobrania z bazy. Z punktu widzenia usługi właściwej też tylko wywołujemy metodę zdalną i tyle. Pośredniczący w tym procesie adapter pozwala na dodanie różnych dodatkowych funkcjonalności.
Na obecnym etapie wygląda to nie za dobrze, ale wraz z dodawaniem kolejnych metod stworzymy sobie grupę helperów, które będą zajmowały się obsługą parsowania i zapisu do bazy. W rezultacie nasz interfejs będzie robił za „palcowego” (ty zrób to, ty to, a ty to).

Odpalamy

Teraz wystarczy, że do aplikacji dodasz jeden przycisk i w nim wywołasz metodę testFunction. Gotowe…

Na koniec konfiguracja spring.xml

Listing 4. spring.xml

<bean class="pl.koziolekweb.vaadin.codecompiler.data.dao.TestResponseDaoImpl" id="testResponseDao"></bean><bean class="pl.koziolekweb.vaadin.codecompiler.api.IdeoneCodeCompilerService" id="ideoneCompilerService"><property name="serviceUser"><ref bean="ideoneServiceUser"></ref></property><property name="servicePort"><ref bean="ideoneServicePort"></ref></property><property name="responseParser"><ref bean="responseParser"></ref></property><property name="testResponseDao"><ref bean="testResponseDao"></ref></property></bean><bean class="pl.koziolekweb.vaadin.codecompiler.api.parsers.TestResponseParser" id="testResponseParser"></bean><bean class="pl.koziolekweb.vaadin.codecompiler.data.ServiceUser" id="ideoneServiceUser"><constructor-arg value="${ideone.user}"></constructor-arg><constructor-arg value="${ideone.pass}"></constructor-arg></bean><bean class="pl.koziolekweb.vaadin.codecompiler.com.ideone.api.IdeoneServiceServiceLocator" id="ideoneServicePortFactory"></bean><bean class="pl.koziolekweb.vaadin.codecompiler.com.ideone.api.IdeoneServicePort" factory-bean="ideoneServicePortFactory" factory-method="getIdeoneServicePort" id="ideoneServicePort"></bean><bean class="pl.koziolekweb.vaadin.codecompiler.api.parsers.APIResponseParserImpl" id="responseParser"><property name="testResponseParser"><ref bean="testResponseParser"></ref></property></bean>

Po kolejnych zmianach wrzucę projekt na SVNa. Będzie łatwiej 😀