Friday, February 21, 2014

Apache Cxf JAX-WS, JAX-RS Example - Translation Service

Source Code

Problem

Make a translator that uses the services of online translation, this translator must then be published using both as JAX-WS as well as JAX-RS

Solution

To solve this problem we will use Web service Code first (Bottom up) approach.

The online translators that will be used are: Google Translator, Microsoft Translator, Systran Translator

The libs that will be used are: Spring Framework 4.0.0, Apache CXF 3.0.0, HttpClient 4.3.2

Structure

We will follow the next structure:

Basic Structure

  1. Core: It contains the main logic that allows us to make the translation, it is formed by:

    1.1. Application Services These are the direct clients of the domain model. They remain very lightweight, coordinating operations performed against domain objects.

    1.2. Domain This is where the business logic and rules of an application live, and it is the heart of the software.

    1.3. Infrastructure This is where general technical, plumbing - related code happens. This layer acts as a supporting library for all the other layers

  2. Interface: This include the classes needed to be able to publish the services using both as JAX-WS as well as JAX-RS

1. Core

1.1 Application Services:

Main interface

public interface TranslatorService {

    TranslatedText translate(String langFrom, String langTo, String text);
}

Main implementation

@Service
public class TranslatorServiceImpl implements TranslatorService {
    @Autowired
    Translator googleTranslator;
    @Autowired
    Translator microsoftTranslator;
    @Autowired
    Translator systranTranslator;

    public TranslatedText translate(String langFrom, String langTo, String text) {
        LanguageSourceTarget languageSourceTarget = new LanguageSourceTarget(Language.fromString(langFrom), Language.fromString(langTo));
        if (languageSourceTarget.sourceAndTargeAreEquals()) {
            throw new TranslatorException("The languages from and to must be differents.");
        }
        Future<String> googleResult = googleTranslator.translate(languageSourceTarget, text);
        Future<String> systranResult = systranTranslator.translate(languageSourceTarget, text);
        Future<String> microsoftResult = microsoftTranslator.translate(languageSourceTarget, text);
        TranslatedText response = new TranslatedText();
        response.setFrom(languageSourceTarget.getSourceAsStr());
        response.setTo(languageSourceTarget.getTargetAsStr());
        response.setMicrosoftTranslation(getTranslation(microsoftResult));
        response.setGoogleTranslation(getTranslation(googleResult));
        response.setSystranTranslation(getTranslation(systranResult));
        return response;
    }

Notes:

  • In this method the 3 translators are called.
  • Future is used in order to execute the translators asynchronously
  • The return class TranslatedText has the 3 translations

1.2. Domain

Main Interface

public interface Translator {

    public Future<String> translate(LanguageSourceTarget languageSourceTarget, String text);
    public String detectLanguage(String text);
}

The pair of languages to be used in translation, is represented by:

public class LanguageSourceTarget {
    private Language source;
    private Language target;

The language to be used in translation, is represented by:

public enum Language {
    AFRIKAANS("af"),
    ALBANIAN("sq"),
    ...

The result of the translation is represented by:

public class TranslatedText {
    private String from;
    private String to;
    private String googleTranslation;
    private String microsoftTranslation;
    private String systranTranslation;

1.3. Infrastructure

Base Implementation of the translators (Template Method)
public abstract class TranslatorImpl implements Translator {
    @Async
    public Future<String> translate(LanguageSourceTarget languageSourceTarget, String text) {
        try {
            String encodedText = URLEncoder.encode(text, ENCODING_UTF_8);
            String from = languageSourceTarget.getSource().asStr();
            String to = languageSourceTarget.getTarget().asStr();
            return new AsyncResult(translateInternal(from, to, text, encodedText));
        } catch (IOException e) {
            LOG.error("Problems translating:" + e.getMessage(), e);
            throw new TranslatorException("Problems translating:" + e.getMessage(), e);
        }
    }

    protected String translateInternal(String from, String to, String text, String encodedText) throws IOException {
        HttpRequestBase requestBase = getHttpRequest(from, to, text, encodedText);
        HttpClient httpclient = HttpClientBuilder.create().build();
        HttpResponse response = httpclient.execute(requestBase);
        HttpEntity responseEntity = response.getEntity();
        String responseAsStr = transformToString(responseEntity);
        if (StringUtils.hasText(responseAsStr)) {
            return getTranslationFrom(responseAsStr);
        }
        return "";
    }

    protected abstract HttpRequestBase getHttpRequest(String from, String to, String text, String encodedText);

    protected abstract String getTranslationFrom(String responseAsStr);

Main methods:

translate:

  • Encode the text using UTF-8
  • Get the string representations of the languages
  • Call to translate internal
  • It is market as Async to mark this method as asynchronous

translateInternal:

  • Get the HttpRequest (GET or POST) to be used.
  • The HttpRequest is invoked using HttpClient, with this we can get the HttpResponse that contains the Entity, which is the response of the translator
  • Convert the Entity to String.
  • Parsing this string we can get the translation

Abstract Methods:

The following methods must be overwritten by each implementation of the translator

HttpRequestBase getHttpRequest(String from, String to, String text, String encodedText)

Return a HttpRequest object that contains: the method (GET or POST), the url that must be called and the parameters that must be sent.

String getTranslationFrom(String responseAsStr)

Receive a String representing the full response of the online translator, and parsing it returns the translation

Google Translator
@Component("googleTranslator")
public class GoogleTranslator extends TranslatorImpl {

    @Override
    protected HttpRequestBase getHttpRequest(String from, String to, String text, String encodedText) {
        HttpGet httpGet = new HttpGet("http://translate.google.com/translate_a/t?client=t&text=" + encodedText + "&hl=" + from + "&sl=" + from + "&tl=" + to + "&multires=1&otf=2&ssel=0&tsel=0&sc=1&ie=" + ENCODING_UTF_8);
        return httpGet;
    }

    protected String getTranslationFrom(String responseAsStr) {
        StringBuilder sb = new StringBuilder();
        if (responseAsStr.length() > 4) {
            int idxEnd = responseAsStr.indexOf("\"", 4);
            sb.append(responseAsStr.substring(4, idxEnd));
        }
        return sb.toString();
    }

Text to translate: "This is a test" (From en to es)

HttpRequestBase GET:

http://translate.google.com/translate_a/t?client=t&text=This+is+a+test&hl=en&sl=en&tl=es&multires=1&otf=2&ssel=0&tsel=0&sc=1&ie=UTF-8

responseAsStr:

[[["Esta es una prueba","This is a test","",""]],,"en",,[["Esta es una prueba",[1],true,false,390,0,4,0]],[["This is a test",1,[["Esta es una prueba",390,true,false],["Esto es una prueba",31,true,false],["esta es una prueba",0,true,false],["Es una prueba",0,true,false],["Este es un examen",0,true,false]],[[0,14]],"This is a test"]],,,[["en"]],103]
Microsoft Translator
@Component("microsoftTranslator")
public class MicrosoftTranslator extends TranslatorImpl {
    private static String API_KEY = "YOUR_API_KEY";

    @Override
    protected HttpRequestBase getHttpRequest(String from, String to, String text, String encodedText) {
        String urlStr = "http://api.microsofttranslator.com/v2/Http.svc/Translate";
        String PARAMETERS = "appId=" + API_KEY + "&text=" + encodedText + "&from=" + from + "&to=" + to + "";
        HttpGet httpget = new HttpGet(urlStr + "?" + PARAMETERS);
        return httpget;
    }

    @Override
    protected String getTranslationFrom(String responseAsStr) {
        return getResultFromResponseStr(responseAsStr);
    }

    public String detectLanguage(String text) {
        try {
            String encodedText = URLEncoder.encode(text, ENCODING_UTF_8);
            String urlStr = "http://api.microsofttranslator.com/v2/Http.svc/Detect";
            String parameters = "appId=" + API_KEY + "&text=" + encodedText;
            HttpGet httpget = new HttpGet(urlStr + "?" + parameters);
            HttpClient httpclient = HttpClientBuilder.create().build();
            HttpResponse response = httpclient.execute(httpget);
            HttpEntity entity = response.getEntity();
            String responseStr = transformToString(entity);
            return getResultFromResponseStr(responseStr);
        } catch (Throwable e) {
            LOG.error("Problems detecting language:" + e.getMessage(), e);
            throw new TranslatorException("Problems detecting language:" + e.getMessage(), e);
        }
    }

    private static String getResultFromResponseStr(String responseAsStr) {
        if (!StringUtils.hasText(responseAsStr)) {
            return "";
        }
        int idxBegin = responseAsStr.indexOf(">");
        int idxEnd = responseAsStr.indexOf("<", idxBegin + 1);
        return responseAsStr.substring(idxBegin + 1, idxEnd);
    }

Text to translate: "This is a test" (From en to es)

HttpRequestBase GET:

http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=YOUR_API_KEY&text=This+is+a+test&from=en&to=es

responseAsStr:

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Esto es una prueba</string>
Systran Translator
@Component("systranTranslator")
public class SystranTranslator extends TranslatorImpl {

    @Override
    protected HttpRequestBase getHttpRequest(String from, String to, String text, String encodedText) {
        String lpStr = from + "_" + to;
        String urlStr = "http://www.systranet.com/sai?gui=text&lp=" + lpStr + "&sessionid=13071170317011544&service=urlmarkuptranslate";
        text = "<html><body>" + text + "<br></body></html>";
        StringEntity entityStr = new StringEntity(text, ENCODING_UTF_8);
        HttpPost httpPost = new HttpPost(urlStr);
        httpPost.setEntity(entityStr);
        return httpPost;
    }

    @Override
    protected String getTranslationFrom(String responseAsStr) {
        String classResult = "<html>";
        int idxBegin = responseAsStr.indexOf(classResult);
        idxBegin = responseAsStr.indexOf(classResult, idxBegin + 1);
        int idxEnd = responseAsStr.length() - 1;
        String htmlResult = responseAsStr.substring(idxBegin, idxEnd);
        String result = SimpleHtmlParser.getInnerText(htmlResult);
        return result != null ? result.trim() : "";
    }

Text to translate: "This is a test" (From en to es)

HttpRequestBase POST:

 http://www.systranet.com/sai?gui=text&lp=en_es&sessionid=13071170317011544&service=urlmarkuptranslate

responseAsStr:

<html><body><span class="systran_seg" id="Sp1.s2_o"><span class="systran_token_word" value="2428/pron" id="p1.t2_1">This</span> <span class="systran_token_word" value="4004/verb:plain" id="p1.t2_2">is</span> <span class="systran_token_word" value="3c3e/det" id="p1.t2_3">a</span> <span class="systran_altmeaning" value="&lt\;reference&gt\;test7f7f&lt\;/reference&gt\;&lt\;choice value='altmeaning-,-31322,-,100'&gt\;[criterio normal] (SYSTRAN Alternative Dictionary)&lt\;/choice&gt\;&lt\;choice value='altmeaning-,-31323,-,100'&gt\;[ensayo] (SYSTRAN Alternative Dictionary)&lt\;/choice&gt\;&lt\;choice value='altmeaning-,-31324,-,100'&gt\;[examen] (SYSTRAN Alternative Dictionary)&lt\;/choice&gt\;&lt\;choice value='_main,0' default='yes'&gt\;[prueba] (General)&lt\;/choice&gt\;&lt\;source&gt\;test&lt\;/source&gt\;" id="altmeaning_1"><span class="systran_token_word" value="1010/noun:common" id="p1.t2_4">test</span></span></span><br></body></html>;<html>
<meta http-equiv="Content-Type" content="text/html\; charset=UTF-8">
<body><span class="systran_seg" id="Sp1.s2_o"><span class="systran_token_word" value="2428/pron" id="p1.t2_1">Esto</span> <span class="systran_token_word" value="4004/verb:plain" id="p1.t2_2">es</span> <span class="systran_token_word" value="*" id="p1.t2_0">una</span> <span class="systran_altmeaning" value="&lt\;reference&gt\;test7f7f&lt\;/reference&gt\;&lt\;choice value='altmeaning-,-31322,-,100'&gt\;[criterio normal] (SYSTRAN Alternative Dictionary)&lt\;/choice&gt\;&lt\;choice value='altmeaning-,-31323,-,100'&gt\;[ensayo] (SYSTRAN Alternative Dictionary)&lt\;/choice&gt\;&lt\;choice value='altmeaning-,-31324,-,100'&gt\;[examen] (SYSTRAN Alternative Dictionary)&lt\;/choice&gt\;&lt\;choice value='_main,0' default='yes'&gt\;[prueba] (General)&lt\;/choice&gt\;&lt\;source&gt\;test&lt\;/source&gt\;" id="altmeaning_1"><span class="systran_token_word" value="1010/noun:common" id="p1.t2_4">prueba</span></span></span><br></body></html>;

Testing Translation Service

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/META-INF/spring/applicationContext.xml"})
public class TranslatorServiceTest {

    @Autowired
    TranslatorService translatorService;
    @Test
    public void translateTest() throws Exception {
        TranslatedText translatedText = translatorService.translate("en", "es", "This is a test of translation service");
        System.out.println(translatedText);
    }

It will print:

TranslatedText{from='en', to='es', googleTranslation='Esta es una prueba de servicio de traducción', microsoftTranslation='Esta es una prueba de servicio de traducción', systranTranslation='Ésta es una prueba del servicio de traducción'}

2. Interface

2.1 JAX-RS

Main Interface

@Path("/")
public interface TranslatorRest {

    @GET
    @Path("translate/{from}/{to}/{text}")
    @Produces({MediaType.APPLICATION_JSON})
    public TranslatedText translate(@PathParam("from") String from, @PathParam("to") String to, @PathParam("text") String text);
}

Main Implementation

@Service
public class TranslatorRestImpl implements TranslatorRest {

    @Autowired
    TranslatorService translatorService;

    public TranslatedText translate(String from, String to, String text) {
        TranslatedText translatedText = translatorService.translate(from, to, text);
        return translatedText;
    }
}

Notes:

  • It is only required to put the JAX-RS annotations in the interface
  • The main method only calls the application service. There is no business logic in these methods
Exception Handler
@Component
public class ExceptionHandler implements ExceptionMapper {

    public javax.ws.rs.core.Response toResponse(Throwable throwable) {
        return Response.serverError().entity(throwable.getMessage()).build();
    }
}
Examples:

Request:

http://services.anotes.org/translator/translate/en/es/task

Response:

{"from":"en","to":"es","googleTranslation":"tarea","microsoftTranslation":"tarea","systranTranslation":"tarea"}
Using filters

We need that this service can support links as the following:

http://services.anotes.org/translator/translate/task

In this there is not any information about the language neither "from" nor "to" but we can infer both of them and convert this link to:

http://services.anotes.org/translator/translate/en/es/task

To infer the "from" language we can use the detectLanguage method of MicrosoftTranslator using the text to translate as parameter To infer the "to" language we can use the language sent in the request header.

To convert the url before calling the TranslatorRest we must use a filter. For this case we will implement ContainerRequestFilter as show below

Container Request Filter

@Component
@Provider
@PreMatching
public class LanguageSetterFilter implements ContainerRequestFilter {
    private static final String DEFAULT_LANG_TO = "es";
    private static final String PATH_TRANSLATE = "/translate/";
    @Autowired
    Translator microsoftTranslator;

    public void filter(ContainerRequestContext ctx) {
        UriInfo uriInfo = ctx.getUriInfo();
        String uriStr = uriInfo.getRequestUri().toString();
        int idx = uriStr.indexOf(PATH_TRANSLATE);
        boolean isTranslatePath = idx != -1;
        if (isTranslatePath) {
            String parameters = uriStr.substring(idx + PATH_TRANSLATE.length());
            boolean existLanguages = parameters.indexOf("/") != -1;
            if (!existLanguages) {
                String headerLanguage = getHeaderLanguage(ctx);
                ctx.setRequestUri(getNewUri(uriStr, idx, parameters, headerLanguage));
            }
        }
    }

    private String getHeaderLanguage(ContainerRequestContext ctx) {
        String headerString = ctx.getHeaderString("accept-language");
        return headerString != null && headerString.length() > 2 ? headerString.substring(0, 2) : DEFAULT_LANG_TO;
    }

    private URI getNewUri(String uriStr, int idx, String parameters, String headerLanguage) {
        String langFrom = microsoftTranslator.detectLanguage(parameters);
        String langTo = headerLanguage;
        String newUri = uriStr.substring(0, idx + PATH_TRANSLATE.length()) + langFrom + "/" + langTo + "/" + parameters;
        try {
            return new URI(newUri);
        } catch (URISyntaxException e) {
            LOG.error("Getting new uri:" + newUri, e);
            throw new TranslatorException("Problems Getting new uri:" + newUri, e);
        }
    }
}

Method

filter:

JAX-RS Bean Declaration
<jaxrs:server address="/translator">
    <jaxrs:serviceBeans>
        <ref bean="translatorRestImpl"/>
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <ref bean="languageSetterFilter"/>
        <ref bean="exceptionHandler"/>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
    </jaxrs:providers>
</jaxrs:server>
Wadl
<application>
    <grammars/>
    <resources base="http://services.gamal-mateo.cloudbees.net/translator">
        <resource path="/">
            <resource path="translate/{from}/{to}/{text}">
                <param name="from" style="template" type="xs:string"/>
                <param name="to" style="template" type="xs:string"/>
                <param name="text" style="template" type="xs:string"/>
                <method name="GET">
                    <request/>
                    <response>
                        <representation mediaType="application/json"/>
                    </response>
                </method>
            </resource>
        </resource>
    </resources>
</application>
Testing
public class TranslatorRestTest {
    private final static String ENDPOINT_ADDRESS = "http://localhost:8062/apache-cxf-example/translator";
    public static final String ENCODING_UTF_8 = "UTF-8";

    @Test
    public void translateTest() throws IOException {
        Client client = ClientBuilder.newClient();
        Response response = client.target(ENDPOINT_ADDRESS + "/translate/es/en/prueba").request("application/json").get();
        String responseStr = getResponseAsStr(response);
        System.out.println(responseStr);
    }

It will print:

{"from":"es","to":"en","googleTranslation":"test","microsoftTranslation":"","systranTranslation":"test"}

2.1 JAX-WS

Main Interface

@WebService
public interface TranslatorPT {

    @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
    @WebMethod(action = "http://anotes.org/services/translator/ws/translate")
    @WebResult(name = "TranslateResponse", targetNamespace = "http://anotes.org/services/translator/ws/schema", partName = "part")
    @ResponseWrapper(targetNamespace = "http://anotes.org/services/translator/ws/schema", className = "org.anotes.services.translator.ws.schema.TranslateResponse")
    @RequestWrapper(targetNamespace = "http://anotes.org/services/translator/ws/schema", className = "org.anotes.services.translator.ws.schema.TranslateRequest")
    public TranslateResponse translate(@WebParam(name = "translateRequest",
            partName = "part",
            targetNamespace = "http://anotes.org/services/translator/ws/schema")
                                       TranslateRequest request);

}

Main Implementation

@Service
@WebService(portName = "translatorPort",
        serviceName = "translatorService",
        targetNamespace = "http://anotes.org/services/translator/ws",
        endpointInterface = "org.anotes.services.translator.ws.TranslatorPT")
public class TranslatorPTImpl implements TranslatorPT {

    @Autowired
    TranslatorService translatorService;

    @Autowired
    ConversionService conversionService;

    public TranslateResponse translate(TranslateRequest request) {
        TranslatedText translatedText = translatorService.translate(request.getLangFrom(), request.getLangTo(), request.getText());
        TranslateResponse response = conversionService.convert(translatedText, TranslateResponse.class);
        return response;
    }
}

Notes:

  • It is only required to put the full JAX-WS annotations in the interface methods
  • It is required to put the full class JAX-WS @WebService annotations in the implementation class
  • The main method only calls the application service. There is no business logic in these methods

Request

public class TranslateRequest {
    private String langFrom;
    private String langTo;
    private String text;

Response

public class TranslateResponse {
    private ResultEnum resultCode = ResultEnum.OK;
    private String errorMsg;
    private String googleTranslation;
    private String microsoftTranslation;
    private String systranTranslation;

package-info It is needed in order to point where will be generated the objects for this web service

@javax.xml.bind.annotation.XmlSchema(namespace = "http://anotes.org/services/translator/ws/schema")
package org.anotes.services.translator.ws.schema;
Exception Handler

We will use spring-aop; specifically we will use the following aspect in order to intercept all calls to public methods of the *PTImpl class; and in case of exception we will generate an object of TranslateResponse setting the result code and the error message

@Aspect
@Component
public class WsServicesAspect {
    protected final Logger LOG = LoggerFactory.getLogger(getClass());

    @Pointcut("bean(*PTImpl)")
    private void serviceBean() {
    }

    @Pointcut("execution(public * *(..))")
    private void publicMethod() {
    }

    @Around("serviceBean() && publicMethod()")
    public Object processServicePtPublicMethods(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String method = proceedingJoinPoint.getSignature().toShortString();
        try {
            LOG.info("Around: Before executing:" + method);
            Object obj = proceedingJoinPoint.proceed();
            LOG.info("Around: After executing:" + method);
            return obj;
        } catch (Throwable throwable) {
            LOG.error("Problems calling the method: " + method, throwable);
            String errorMsg = throwable.getMessage();
            Object responseObj = getResponseInstance(proceedingJoinPoint);
            BeanWrapper beanWrapper = new BeanWrapperImpl(responseObj);
            beanWrapper.setPropertyValue("resultCode", ResultEnum.ERROR);
            beanWrapper.setPropertyValue("errorMsg", errorMsg);
            return responseObj;
        }
    }
JAX-WS Bean Declaration
<!-- JAX-WS -->
<jaxws:endpoint address="/translatorPort"
                implementor="#translatorPTImpl"/>
Wsdl
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:tns="http://anotes.org/services/translator/ws"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:ns1="http://ws.translator.services.anotes.org/" name="translatorService"
                  targetNamespace="http://anotes.org/services/translator/ws">
    <wsdl:import location="http://services.gamal-mateo.cloudbees.net/translatorPort?wsdl=TranslatorPT.wsdl"
                 namespace="http://ws.translator.services.anotes.org/"></wsdl:import>
    <wsdl:binding name="translatorServiceSoapBinding" type="ns1:TranslatorPT">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="translate">
            <soap:operation soapAction="http://anotes.org/services/translator/ws/translate" style="document"/>
            <wsdl:input name="translate">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="translateResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="translatorService">
        <wsdl:port binding="tns:translatorServiceSoapBinding" name="translatorPort">
            <soap:address location="http://services.gamal-mateo.cloudbees.net/translatorPort"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
Testing
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/META-INF/spring/spring-context-test.xml"})
public class TranslatorPTTest {

    @Autowired
    private TranslatorPT wsClient;

    @Test
    public void translateTest() {
        TranslateRequest request = new TranslateRequest();
        request.setLangFrom("es");
        request.setLangTo("en");
        request.setText("Esta es una prueba de JAXWS");
        TranslateResponse response = wsClient.translate(request);
        System.out.println(response);
    }

It will print:

TranslateResponse{resultCode=OK, errorMsg='null', googleTranslation='This is a test of JAXWS', microsoftTranslation='', systranTranslation='This is a test of JAXWS'}

Final Notes:

  • Microsoft translator needs an API KEY, so you have to request one and then put it on the private static String API_KEY = "YOUR_API_KEY" in MicrosoftTranslator

  • Gradle is used so you can run the application executing: gradle jettyrun

Sunday, October 6, 2013

One way to handle all the information of the programmer

Checklist

To check if you need to read this post you should have several of the following characteristics:
  • You are nearly all day at a computer
  • You manage several projects simultaneously
  • You record some pieces of code or data
  • You collect and read several documents in different formats: pdfs, docs, xlsx, etc and take notes of the main parts
  • You are subscribed to several RSS
  • You collect url links to pages that could possibly be useful in the future.

Problem

Day by day we get too much information. this is the reason why we need to arrange it to be able to handle and process it in a better way.
Taking into account that our finite resource is time; and therefore will generate many bottlenecks in the way of our professional development; we need to take control of the time that we dedicate to each of our tasks to align its use with our objectives to medium and long term.
To solve or at least alleviate this problem I developed ATaskBucket which is a desktop program that helps us to organize and handle all the information that we have.

Steps to keep organized all our information.

At the beginning we must do the following:

  1. Identify/Create the main repositories Buckets in which we can place all our tasks. These repositories must be hierarchical
  2. Identify/Create the major tasks of each bucket. Time should be set for these tasks to be executed.
  3. Each bucket/task can have associated one or more resources (files)
  4. For each task at least create the following files:
    • Notes.txt : It will contain any note related to the task.
    • Tasks.task : It will contain the small tasks that come out in the process of developing the main task.

In the day to day

  1. Use the dashboard to see the tasks assigned to be made that day. This dashboard will display the tasks that were created as:
    • asap
    • daily
    • weekly (if the day matches with the current one)
    • monthtly (if the date matches with the current one)
    • at (If it was created to be executed that current date)
  2. Before you start to perform some task click Start. This way we mark the start of the task and we can initialize the timer and keep track of the time taken to execute a task. When you need to perform another task you can click on:
    • stop (If you wish to stop the execution of the task),
    • finish (If you already finished executing the task),
    • pospone (If we want to postpone the task for another day)

Weekend review.

Check the time spent on tasks and buckets, so that we can:
  1. Know:
    • How much time we spent on each bucket
    • How much time we spent on each task
    • In which tasks we spent our time in this week
    • Our pattern of execution of tasks
  2. Set goals such as:
    • The next week I will devote more time to this bucket
    • I will avoid wasting time on this another bucket
  3. Modify the periodicity of the tasks:
    • This task should have a periodicity of daily (so that it appears in our daily dashboard)
    • This task should have a "anytime" periodicity (so that it does not appear in our dashboard, unless we add explicitly)

Tools

  1. GoTo: It allows us to show another task at any time.
  2. Full Text Search: It allows us to search in the data of the buckets, the tasks and all the resources associated with them using full text search
  3. Save information from pdfs that we are reading. For instance, we can save the page where we stayed, or the zoom with which normally we read.
  4. Feeds Reader. It allows us to subscribe to RSS and see the titles of the entries.

Cloud Friendly

The application saves all of the information in a directory; in this way if we make this directory a directory managed by DropBox, Google Drive or other similar; all of our information will be in the cloud and you can have access to it from any computer.

Example of structure

We can use the following structure :

For general tasks (B: Bucket, T:Task):

(B) General
    (T) Meals
    (T) Breaks

For reading assignments (B: Bucket, T:Task, R:Resource):

(B) Reading
    (B) General
        (T) News
        (T) Rss
    (B) Programming
        (T) Read about JQuery
            (R) Notes.txt (Here we can put some excerpts that are interesting for us)
            (R) Novice to Ninja.pdf
        (T) Read about Solr
            (R) Notes.txt

For writing assignments (B: Bucket, T:Task, R:Resource):

(B) Writing
    (B) Programming
        (B) Aplication Design
            (T) Write about DDD applied
    (B) Managment
        (T) Write about time managment
            (R) Blog Entry.md

To handle the tasks of a project (B: Bucket, T:Task, R:Resource):

(B) Project Name 
    (B) Phase I
        (T) Development
            (R) Notes.txt  (Here we can put some things that we want to remember)
            (R) Tasks.task (Here we can put the specific tasks that we are doing each day.)
        (T) Review
            (R) Notes.txt (Here we can put the observations that we found at the time of reviewing the progress of the project)
        (T) Quality Assurance
            (R) QA Report

Thursday, March 14, 2013

Springframework - Using annotations to register Custom Converters

Source Code: Example. Springframework 3.2.1.RELEASE

Problem

In order to register custom converters we have to do the following:

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
     <property name="converters">
         <list>
             <!-- list of converters-->
             <bean class="org.anotes.springexample.converter.PersonToClient" />
             <bean class="org.anotes.springexample.converter.ClientToPerson" />
             ...
         </list>
     </property>
 </bean>

We want to register the converters using only some custom annotation.

Solution

We have to follow the next steps:

Create the annotation

This annotation will be used to mark the classes that are converters

package org.anotes.spring.stereotype;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TypeConverter {

}

Create the custom BeanDefinitionRegistryPostProcessor

We need to do the following tasks: 1. Add programmatically the bean "conversionService" to the applicationContext 2. Add all the custom converters (identified by the annotation TypeConverter) to the bean "conversionService" define in the previous step.

To complete these tasks we need to create a custom BeanDefinitionRegistryPostProcessor

public class ConverterRegistryPostProcessor implements  BeanDefinitionRegistryPostProcessor, BeanPostProcessor, ApplicationContextAware {

private final String CONVERSION_SERVICE_NAME = "conversionService";

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry                    registry) throws BeansException {
    registry.registerBeanDefinition(CONVERSION_SERVICE_NAME, BeanDefinitionBuilder.rootBeanDefinition(ConversionServiceFactoryBean.class).getBeanDefinition());
}

public void postProcessBeanFactory(ConfigurableListableBeanFactory          beanFactory) throws BeansException {    
}

public Object postProcessBeforeInitialization(Object bean, String           beanName) throws BeansException {
    if (CONVERSION_SERVICE_NAME.equals(beanName)) {
        Map<String, Converter> beansOfType = appCtx.getBeansOfType(Converter.class);
        ConversionServiceFactoryBean conversionfactoryBean = (ConversionServiceFactoryBean) bean;
        Set converters = new HashSet(beansOfType.values());
        conversionfactoryBean.setConverters(converters);
    }
    return bean;
}

public Object postProcessAfterInitialization(Object bean, String            beanName) throws BeansException {
    return bean;
}

ApplicationContext appCtx;

public void setApplicationContext(ApplicationContext                        applicationContext) throws BeansException {
    appCtx = applicationContext;
}

}

In the method: "postProcessBeanDefinitionRegistry" the "conversionService" is added to the context.

In the method: "postProcessBeanFactory" we collect all the beans that have the annotation "TypeConverter" and then add all of these to the conversion service.

Take into account that as the converters are beans you can autowired other beans in these converters.

Register the PostProcessor

We have to include in the application context the post processor as bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:component-scan base-package="org.anotes.springexample"/>

    <bean class="org.anotes.spring.postprocessor.ConverterRegistryPostProcessor"/>

</beans>

Test

We have the following converter:

@TypeConverter
public class ClientToPerson implements Converter<Client,Person> {
    @Override
    public Person convert(Client source) {
        Person person = new Person();
        BeanUtils.copyProperties(source,person);
        return person;
    }
}

We test that the converter function properly with :

public static void main(String[] args) {
    String configFiles = "classpath*:/app-context.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configFiles);
    Client client = createClient();
    ConversionService conversionService = (ConversionService) context.getBean("conversionService");
    Person person = conversionService.convert(client, Person.class);
    logger.info("Client:{}", client );
    logger.info("Person:{}", person );
}

And we get the following:

springexample.Main Client:Client{name='Joseph', gender='M', address='St Main Square', subscriptionDate=Thu Mar 14 20:37:29 COT 2013} [INFO ]
springexample.Main Person:Person{name='Joseph', gender='M', address='St Main Square'} [INFO ]

With the above we see that all function correctly.

Sunday, January 13, 2013

Generating Java Web Start files using Gradle

Source Code: Example.

Problem

We have a Java Swing program and we need to create the files needed to use webstart in order to execute our program.

Solution

We have to follow the next steps:

Generate the key file

Using the keytool.exe program that is in "\bin" we will generate the key using:

keytool -genkey -alias http://www.anotes.org -keystore D:/temp/anotes.keys

Next to this we have to answer all the questions; at the end the file "anotes.keys" will have been generated

We have to copy this file to: "\src\jnlp\keys"

Create the graddle tasks

We need to do the following tasks:

  • Generate the jar of the project
  • Collect all jars and copy to specific directory
  • Sign all jars
  • Generate the jnlp files
  • Create the public task that will do all the process

Generate the jar of the project

Gradle has the "jar" task to do this.

Collect all jars and copy to specific directory

We have to create the following gradle task:

task copyFiles(type: Copy, dependsOn: [jar]) {
    from configurations.runtime
    from("build/libs")
    into("webstart/lib")
    include('*.jar')
}

In essence we are copying all jars ('*.jar') from both "configurations.runtime" (all dependent jars) and "build/libs" (all project jars) to "webstart/lib"

Sign all jars

We have to create the following gradle task:

task signAll(dependsOn: [copyFiles]) << {
    new File('webstart/signed').mkdirs()
    def libFiles = files { file('webstart/lib').listFiles() }
    libFiles.each {
        ant.signjar(
                destDir: 'webstart/signed',
                alias: 'http://www.anotes.org',
                jar: it,
                keystore: 'src/jnlp/keys/anotes.keys',
                storepass: 'anotes123',
                preservelastmodified: 'true')
    }
}

This task make the following:

  • Create directory "webstart/signed"
  • Sign all files that are in "webstart/lib" and put these ones in "webstart/signed". To do the signing the file "anotes.keys" is used.

Generate the jnlp files

We have to create the following task:

task generateJnlp(dependsOn: [signAll]) << {
    File jnlpTemplateFile = new File('src/jnlp/template/template.vm')
    def root = new XmlParser().parse(jnlpTemplateFile)
    def jnlpFileName = "gradle-webstart-example-${version}.jnlp"
    // Setting values in the jnlp template
    // Setting root values
    // File CodeBase
    // root.@codebase = 'file:/'+new File('webstart/signed').absolutePath
    // Http CodeBase
    root.@codebase = 'https://dl.dropbox.com/u/24028482/jnlp/gradle-webstart-example'
    root.@href = jnlpFileName
    // Setting information values
    def information = root.information[0]
    def title = information.title[0]
    def vendor = information.vendor[0]
    title.setValue("Gradle Webstart example")
    vendor.setValue("ANotes")
    def descriptionList = information.description
    descriptionList.each {
        it.setValue("Simple example of generating webstart files using gradle")
    }
    // Setting resources
    def resource = root.resources[0]
    def j2se = resource.j2se[0]
    j2se.@version = "1.6+"
    def collection = files { file('webstart/lib').listFiles() }
    def mainJar = "gradle-webstart-example-${version}.jar"
    collection.each {
        def resourceValues = [href: it.name]
        if (mainJar == it.name) {
            resourceValues = [href: it.name, main: 'true']
        }
        resource.appendNode('jar', resourceValues)
    }
    // Setting the main class
    def applicationDesc = root.'application-desc'[0]
    applicationDesc.'@main-class' = "org.anotes.example.gradlewebstart.MainApp"
    // Writing the jnlp file filled
    File jnlpFile = new File('webstart/signed/' + jnlpFileName)
    new XmlNodePrinter(new PrintWriter(jnlpFile)).print(root)
}

This task make the following:

  • Load the jnlp template from "src/jnlp/template/template.vm" using the XmlParser; that will facilitate the operations of reading and writing of the xml.
  • Setting values in the jnlp template

    • Setting codebase. If you want to use local file system uncomment the following:

      // File CodeBase    
      //root.@codebase = 'file:/'+new File('webstart/signed').absolutePath
      

      If you want to use url reference mantain the following (change the url for your specific path):

      // Http CodeBase
      root.@codebase = 'https://dl.dropbox.com/u/24028482/jnlp/gradle-webstart-example'
      
    • Setting information values

    • Setting resources - Write all the jar names, put "main=true" to the project jar
    • Setting the main Class
  • Save the new xml to:webstart/signed/gradle-webstart-example-1.jnlp"

Create the public task that will do all the process

task generateWebStartFiles (dependsOn: [generateJnlp])<< {
    println("Generating all webstart files in:webstart/signed")
}

From the command line you have to execute the following:

gradle generateWebStartFiles

and all the webstart files will be in: "webstart/signed/"

Saturday, April 14, 2012

Spring AOP–Examples

Source Code:Example

Setting Context

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
    <aop:aspectj-autoproxy/>
    <context:component-scan base-package="org.anotes.springexample.aop"/>
</beans>

Before, After, Around (example1)

Objects

public class Person {
    protected String name;

    public void walk(){
        logger.info(name+" is walking");
    }

@Component
@Scope("prototype")
public class Painter extends Person{

    public void paint(){
        logger.info(name+" is painting");
    }

}

@Component
@Scope("prototype")
public class Teacher extends Person {

    public void teach(){
        logger.info(name+" is teaching");
    }
}

Aspect

@Aspect
@Component("aspectExample1")
public class AspectExample {
    protected final Logger logger = LoggerFactory.getLogger(getClass());


    @Pointcut("execution(* org.anotes.springexample.aop.example1.*.walk(..))")
    private void anyWalking() {
    }


    @Pointcut("execution(* org.anotes.springexample.aop.example1.*.*(..)) && bean(teacher)")
    private void teacherMethods() {
    }


    @Pointcut("within(org.anotes.springexample.aop.example1.*))")
    public void inPainterAndTeacherMethods() {
    }


    @Before("anyWalking()")
    public void logBeforeAnyWalking(JoinPoint joinPoint) {
        logger.info("Before walking:{}", joinPoint.getTarget());
    }


    @After("anyWalking()")
    public void logAfterAnyWalking() {
        logger.info("After walking");
    }


    @Before("teacherMethods() && args(param)")
    public void logPainterAndTeacherMethodWithStringArg(String param) {
        logger.info("Teacher Methods:Before methods with String parameter:{}", param);
    }


    @Around("inPainterAndTeacherMethods()")
    public void logAllMethodsPainterAndTeacher(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.info("Around: Before executing:" + proceedingJoinPoint.getSignature());
        proceedingJoinPoint.proceed();
        logger.info("Around: After executing:" + proceedingJoinPoint.getSignature());
    }

}

Lines

Line 5: Define a Pointcut that point to “walk” method of any Class that are in the “example1” package

Line 8: Define a Pointcut that point to all methods of the bean “teacher”

Line 11: Define a Pointcut that point to any method of  any Class that are in the “example1” package

Line 15: Define a Before Advice that will be called before executing any “walk” method

Line 19: Define a After Advice that will be called before executing any “walk” method

Line 23: Define a Before Advice that will be called before executing any method in the “teacher” bean that has at first parameter a String

Line 27: Define a Around Advice that will be called before executing any method in the “painter” and “teacher” bean

Code to test the Advice

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        String configFiles = "classpath*:/app-context.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configFiles);
        example1(context);
    }
    private static void example1(ApplicationContext context) {
        logger.info("EXAMPLE 1 - Before, After, Around ");
        Teacher teacher = context.getBean(Teacher.class);
        teacher.setName("Ross");
        Teacher teacher2 = context.getBean(Teacher.class);
        teacher2.setName("Jhon");
        Painter painter = context.getBean(Painter.class);
        painter.setName("Pedro");
        teacher.walk();
        painter.walk();
    }

Output

            aop.Main EXAMPLE 1 - Before, After, Around  [INFO ]
aspect.AspectExample Teacher Methods:Before methods with String parameter:Ross [INFO ]
aspect.AspectExample Around: Before executing:void org.anotes.springexample.aop.example1.Person.setName(String) [INFO ]
aspect.AspectExample Around: After executing:void org.anotes.springexample.aop.example1.Person.setName(String) [INFO ]
aspect.AspectExample Teacher Methods:Before methods with String parameter:Jhon [INFO ]
aspect.AspectExample Around: Before executing:void org.anotes.springexample.aop.example1.Person.setName(String) [INFO ]
aspect.AspectExample Around: After executing:void org.anotes.springexample.aop.example1.Person.setName(String) [INFO ]
aspect.AspectExample Around: Before executing:void org.anotes.springexample.aop.example1.Person.setName(String) [INFO ]
aspect.AspectExample Around: After executing:void org.anotes.springexample.aop.example1.Person.setName(String) [INFO ]
aspect.AspectExample Before walking:org.anotes.springexample.aop.example1.Teacher@1989b5 [INFO ]
aspect.AspectExample Around: Before executing:void org.anotes.springexample.aop.example1.Person.walk() [INFO ]
    example1.Teacher Ross is walking [INFO ]
aspect.AspectExample After walking [INFO ]
aspect.AspectExample Around: After executing:void org.anotes.springexample.aop.example1.Person.walk() [INFO ]
aspect.AspectExample Before walking:org.anotes.springexample.aop.example1.Painter@c3c315 [INFO ]
aspect.AspectExample Around: Before executing:void org.anotes.springexample.aop.example1.Person.walk() [INFO ]
    example1.Painter Pedro is walking [INFO ]
aspect.AspectExample After walking [INFO ]
aspect.AspectExample Around: After executing:void org.anotes.springexample.aop.example1.Person.walk() [INFO ]

Introductions / Mixins (example2)

Objects

public interface Resource {
     public void setContent(String content);
     public String getContent();

}
@Component
@Scope("prototype")
public class ResourceImpl implements Resource{
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private String content;
    @Override
    public void setContent(String content) {
        this.content=content;
        logger.info("Current Content:"+content);
    }

public interface Lockable {
    boolean isLocked();
    void lock();
    void unlock();
}
public class LockableImpl implements Lockable {
    private boolean lock = false;

    @Override
    public boolean isLocked() {
        return lock;
    }

    @Override
    public void lock() {
        lock= true;
    }

    @Override
    public void unlock() {
        lock =false;
    }
}

Aspect

@Aspect
@Component("aspectExample2")
public class AspectExample {
    protected final Logger logger = LoggerFactory.getLogger(getClass());


    @DeclareParents(value = "org.anotes.springexample.aop.example2.Resource*", defaultImpl = LockableImpl.class)
    private Lockable lockableSupport;


    @Pointcut("execution(* org.anotes.springexample.aop.example2.Resource.setContent(..))")
    private void settingContent() {
    }


    @Around("settingContent() && this(lockable)")
    private void onSettingContent(ProceedingJoinPoint joinPoint, Lockable lockable) throws Throwable {
        logger.info("Before Setting Content on Resource. Locked:" + lockable.isLocked());
        if (!lockable.isLocked()) {
            joinPoint.proceed();
            logger.info("After Setting Content:");
        } else {
            logger.info("The resource is locked so it is not possible to set its content");
        }
    }
}

Lines

Line 5: Define that the beans which class name begin with “Resource” and are in the example2 package will implement the “Lockable” interface and the default implementation will be provided by “LockableImpl” class

Line 7: Define a Pointcut that point to the “setContent” method of the beans of type Resource

Line 10: Define a Around Advice that will be called before executing the method “setContent” also the bean will be sent as a parameter implementing the Lockable interface

Code to test the introduction

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        String configFiles = "classpath*:/app-context.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configFiles);
        example1(context);
example2(context);
    }

    private static void example2(ApplicationContext context) {
        logger.info("EXAMPLE 2 - Using Introductions ");
        Resource resource = context.getBean(Resource.class);
        resource.setContent("String resource 1");
        Lockable lockable = (Lockable) resource;
        lockable.lock();
        resource.setContent("String resource 2");
        lockable.unlock();
        resource.setContent("String resource 3");
    }


}

Output

aspect.AspectExample Before Setting Content on Resource. Locked:false [INFO ]
example2.ResourceImpl Current Content:String resource 1 [INFO]
aspect.AspectExample After Setting Content: [INFO ]
aspect.AspectExample Before Setting Content on Resource. Locked:true [INFO ]
aspect.AspectExample The resource is locked so it is not possible to set its content [INFO ]
aspect.AspectExample Before Setting Content on Resource. Locked:false [INFO ]
example2.ResourceImpl Current Content:String resource 3 [INFO ]
aspect.AspectExample After Setting Content: [INFO]

Spring Expression Language (SpEL).

Getting properties file

<util:properties id="settings" location="classpath:settings.properties"/>

Accesing a value in properties

<property name="accessToken" value="#{settings['twitter.accessToken']}"/>

systemEnvironment and systemProperties

In addition to reading properties from a <util:properties>-declared collection, Spring makes two special selections of properties available to SpEL:  systemEnvironment and systemProperties.
systemEnvironment contains all of the environment variables on the machine running the application. It’s just a java.util.Properties collection, so the square braces can be used to access its members by their key. For example, we can inject the user’s region into a bean property like this:

<property name="userRegion" value="#{ systemProperties['user.region'] }"/>

Selecting Collection Members

Let’s say that you want to narrow the list of cities down to only those whose population is greater than 100,000. One way to do this is to wire the entire cities bean into a property and place the burden of sifting out the smaller cities on the receiving bean. But with SpEL, it’s a simple matter of using a selection operator (.?[]) when doing the wiring:

<property name="bigCities" value="#{cities.?[population gt 100000]}"/>

SpEL also offers two other selection operators, .^[] and .$[] , for selecting the first and last matching items (respectively) from a collection. For example, to select the first big city from cities:

<property name="aBigCity" value="#{cities.^[population gt 100000]}"/>

No ordering is done on the collection prior to selection, so the City representing Chicago would be wired into the aBigCity property. Likewise, the City object representing El Paso could be selected as follows:

<property name="aBigCity" value="#{cities.$[population gt 100000]}"/>

Projecting Collections

Collection projection involves collecting a particular property from each of the members of a collection into a new collection. SpEL’s projection operator (.![]) can do exactly that.
For example, suppose that instead of a list of City objects, what you want is just a list of String objects containing the names of the cities. To get a list of just the city names, you could wire a cityNames property like this:

<property name="cityNames" value="#{cities.![name]}"/>

But projection isn’t limited to projecting a single property. With a slight change to the previous example, you can get a list of city and state names:

<property name="cityNames" value="#{cities.![name + ', ' + state]}"/>

To bring collection selection and projection together. Here’s how you might wire a list of only big city names into the cityNames property:

<property name="cityNames" value="#{cities.?[population gt 100000].![name + ', ' + state]}"/>

Autowiring

Spring 3 supports a few different annotations for autowiring:

Spring’s own @Autowired annotation
The @Inject annotation from JSR-330
The @Resource annotation from JSR-250

Using expressions with annotation injection

As long as you’re using annotations to autowire bean references into your Spring beans, you may want to also use annotations to wire simpler values. Spring 3.0 introduced @Value, a new wiring annotation that lets you wire primitive values such as int, boolean, and String using annotations.

The @Value annotation is simple to use but, as you’ll soon see, is also powerful. To use it, annotate a property, method, or method parameter with @Value and pass in a String expression to be wired into the property. For example:

@Value("Eruption")
private String song;

Here we’re wiring a String value into a String property. But the String parameter passed into @Value is just an expression—it can evaluate down to any type and thus @Value can be applied to just about any kind of property.

As it turns out, simple values aren’t where @Value shines. Instead, @Value finds its power with SpEL expressions. Recall that SpEL lets you dynamically evaluate complex expressions, at runtime, into values to be wired into bean properties. That makes @Value a powerful wiring option. For example, rather than hardcoding a static value into the song property, let’s use SpEL to pull a value from a system property:

@Value("#{systemProperties.myFavoriteSong}")
private String song;

Wednesday, April 11, 2012

Growing Object Oriented Software, Guided by Tests–Extracts

Source: Growing Object Oriented Software, Guided by Tests

Roles, Responsibilities, Collaborators

We try to think about objects in terms of roles, responsibilities, and collaborators, as best described by Wirfs-Brock and McKean.

  • An object is an implementation of one or more roles;
  • a role is a set of related responsibilities;
  • and a responsibility is an obligation to perform a task or know information.
  • A collaboration is an interaction of objects or roles (or both).

Sometimes we step away from the keyboard and use an informal design technique that Wirfs-Brock and McKean describe, called CRC cards (Candidates, Responsibilities, Collaborators). The idea is to use low-tech index cards to explore the potential object structure of an application, or a part of it. These index cards allow us to experiment with structure without getting stuck in detail or becoming too attached to an early solution.

Tell, Don’t Ask

We have objects sending each other messages, so what do they say? Our experience is that the calling object should describe what it wants in terms of the role that its neighbor plays, and let the called object decide how to make that happen.

This is commonly known as the “Tell, Don’t Ask” style or, more formally, the Law of Demeter. Objects make their decisions based only on the information they hold internally or that which came with the triggering message; they avoid navigating to other objects to make things happen. Followed consistently, this style produces more flexible code because it’s easy to swap objects that play the same role. The caller sees nothing of their internal structure or the structure of the rest of the system behind the role interface.

Designing for Maintainability

We grow our systems a slice of functionality at a time. As the code scales up, the only way we can continue to understand and maintain it is by structuring the functionality:

  • into objects,
  • objects into packages,
  • packages into programs,
  • and programs into systems.

We use two principal heuristics to guide this structuring:

Separation of concerns

When we have to change the behavior of a system, we want to change as little code as possible. If all the relevant changes are in one area of code, we don’t have to hunt around the system to get the job done. Because we cannot predict when we will have to change any particular part of the system, we gather together code that will change for the same reason. For example, code to unpack messages from an Internet standard protocol will not change for the same reasons as business code that interprets those messages, so we partition the two concepts into different packages.

Higher levels of abstraction

The only way for humans to deal with complexity is to avoid it, by working at higher levels of abstraction. We can get more done if we program by combining components of useful functionality rather than manipulating variables and control flow; that’s why most people order food from a menu in terms of dishes, rather than detail the recipes used to create them.

Encapsulation and Information Hiding

We want to be careful with the distinction between “encapsulation” and “information hiding.” The terms are often used interchangeably but actually refer to two separate, and largely orthogonal, qualities:

Encapsulation

Ensures that the behavior of an object can only be affected through its API. It lets us control how much a change to one object will impact other parts of the system by ensuring that there are no unexpected dependencies between unrelated components.

Information hiding

Conceals how an object implements its functionality behind the abstraction of its API. It lets us work with higher abstractions by ignoring lower-level details that are unrelated to the task at hand.

We’re most aware of encapsulation when we haven’t got it. When working with badly encapsulated code, we spend too much time tracing what the potential effects of a change might be, looking at where objects are created, what common data they hold, and where their contents are referenced.

Object Peer Stereotypes

We have objects with single responsibilities, communicating with their peers through messages in clean APIs, but what do they say to each other?

We categorize an object’s peers (loosely) into three types of relationship. An object might have:

Dependencies

Services that the object requires from its peers so it can perform its responsibilities. The object cannot function without these services. It should not be possible to create the object without them. For example, a graphics package will need something like a screen or canvas to draw on—it doesn’t make sense without one.

Notifications

Peers that need to be kept up to date with the object’s activity. The object will notify interested peers whenever it changes state or performs a significant action. Notifications are “fire and forget”; the object either knows nor cares which peers are listening. Notifications are so useful because they decouple objects from each other. For example, in a user interface system, a button component promises to notify any registered listeners when it’s clicked, but does not know what those listeners will do. Similarly, the listeners expect to be called but know nothing of the way the user interface dispatches its events.

Adjustments

Peers that adjust the object’s behavior to the wider needs of the system. This includes policy objects that make decisions on the object’s behalf (the Strategy pattern) and component parts of the object if it’s a composite. For example, a Swing JTable will ask a TableCellRenderer to draw a cell’s value, perhaps as RGB (Red, Green, Blue) values for a color. If we change the renderer, the table will change its presentation, now displaying the HSB (Hue, Saturation, Brightness) values.

Interface and Protocol

An interface describes whether two components will fit together, while a protocol describes whether they will work together.