Sonntag, 12. Oktober 2014

BARACUS from Scratch - Part 9 - Using interface and implementations & context bootstrap


Table of Contents

In this tutorial I am going to explain You how to use the BARACUS framework to work with alternative Implementations of an Interface plus hot-restarting the context to make use of them. Also, I will demonstrate the afterContextInitialized hook which enables You to perform post-container-startup operations.

Previous Tutorial : Lifecycle support with migr8

You can download the sources of this tutorial here

0 - Basics


Since Version 0.8, Baracus offers the capability to register an implementation to an interface. This can be very useful in combination with the bootstrap feature (reinitialize context), because it allows You to switch between several application varieties using different implementations of the interface. The registration is simply an alternative call on BaracusApplicationContext.registerBeanClass.

For this example, we will define a BankAccountLoadService, which we are going to wire twice to the BankAccountDao, logging each call on it.

1 - Defining the interface


Simply define the interface in the service package :


 
public interface BankAccountLoadService {

    List<BankAccount> loadAllAccountsByCustomerId(Long id);

}



2 - Implement the interface 


Next, we implement the interface :

 
public class BankAccountLoadServiceImpl1 implements BankAccountLoadService {



    @Bean
    BankAccountDao bankAccountDao;

    final Logger logger = new Logger(this.getClass());

    @Override
    public List<BankAccount> loadAllAccountsByCustomerId(Long id) {
        // In this demo, this is the primary implementation
        logger.info("Primary implementation called!");
        return bankAccountDao.getByCustomerId(id);

    }

}


3 - Implement the alternative (dummy here)


Then we implement an alternative; this could be a test class, a webservice or whatever. Interface implementations also can be used to improve testability.

 
/**
 * Created by marcus on 30.07.14.
 */
public class BankAccountLoadServiceImpl2 implements BankAccountLoadService {

    @Bean
    BankAccountDao bankAccountDao;

    final Logger logger = new Logger(this.getClass());

    @Override
    public List<BankAccount> loadAllAccountsByCustomerId(Long id) {
        // In this demo, this is the alternative implementation
        logger.info("Alternative implementation called!");
        return bankAccountDao.getByCustomerId(id);
    }
}


4 - Register interface


The interface now is used to register the implementation

 
public class ApplicationContext extends BaracusApplicationContext {

    static {
        ....

        registerBeanClass(BankAccountLoadService.class, BankAccountLoadServiceImpl1.class);

    }
}


This enables the Baracus DI container to inject the implementation wherever the interface is used.


5 - Use interface for injection


Then we will pimp the RowMapper to use the implementation :

public class CustomerDao extends BaseDao<Customer> {



    @Bean

    BankAccountLoadService bankAccountLoadService;

 

...



    private final RowMapper<Customer> rowMapper = new RowMapper<Customer>() {

        @Override

        public Customer from(Cursor c) {



  ....

  

         result.setAccounts(new LazyCollection<BankAccount>(new LazyCollection.LazyLoader<BankAccount>() {

                @Override

                public List<BankAccount> loadReference() {

                    return bankAccountLoadService.loadAllAccountsByCustomerId(id);

                }

            }));



That's all. Now we are able to use the Interface for DI. But that's not all, right? We want to make use of the ability to switch the implementation. Therefore, we will implement a ApplicationContextInitializer setting the Implementation "by random" - just for demonstration purpose. Since the decision, which implementation shall be used only happens on the boot of the app, we use a small semaphore to avoid permanent reinit. Imagine a basic context, you define and then, by configuration data (e.g. thru built-in configurationDao) you decide wether to build a thin client or a fat client - that is the one of the purposes of this class.

public class AfterContextInitialized implements ApplicationContextInitializer {

    static boolean reinit = true;

    @Override
    public void afterContextIsBuilt() {
        if ((new Date().getTime() % 2 ) == 0) {
            ApplicationContext.registerBeanClass(BankAccountLoadService.class, BankAccountLoadServiceImpl1.class);
        } else {
            ApplicationContext.registerBeanClass(BankAccountLoadService.class, BankAccountLoadServiceImpl2.class);
        }
        if (reinit) {
            reinit = false;
            ApplicationContext.reinitializeContext();;
        }
    }
}


This initializer also is registered in the application config :

public class ApplicationContext extends BaracusApplicationContext {

        static { 
          ....
          setApplicationContextInitializer(new AfterContextInitialized());
        }
}


That's it, now You can check by restarting the app several time, which implementation is used by checking the logs when expanding the customer details:

07-30 14:56:51.531    5316-5316/org.baracustutorial I/TUTORIAL_APP﹕ -BankAccountLoadServiceImpl1 Primary implementation called!

other try :

07-30 15:00:55.639    8815-8815/org.baracustutorial I/TUTORIAL_APP﹕ BankAccountLoadServiceImpl2 Alternative implementation called!


Conclusion :


As You can see, the implementation of alternatives can be used - in combination with configuration information - as a very powerful utility to create varying types of implementations / behaviour within the same codebase. The classic can be a client to be run locally using the local dao as a data service or a thin client pipelining all requests thru json requests on a remote system.

Also, it can be very useful to make the software more testable.

Next Tutorial :Writing custom validators




Montag, 6. Oktober 2014

BARACUS from Scratch - Part 10 - Writing custom validators

Table of Contents

Previous Tutorial :Using interfaces & implementations

You can download the sourcecode for this demo here

In this tutorial I will explain you how create custom validation objects to be used within your application. This enables you to have your application shipped with customized validators to fit your personal needs. This tutorial is basing on the FormValidation-Tutorial I published earlier.

Step 1 - Define validator


We want to add a constraint for defining names to our software. A name must start with a capital one letter and continue with zero or more small caps letters. This can be achieved by matching this java regex pattern [A-Z]+[a-z]*. So at first, we will define the constraint message plus the class:

strings.xml:

    <string name="nameViolation">Bad string format. Must be lowercase with starting capital letter</string>


public class NameValidator extends AbstractValidator<String>{



    static final String pattern = "[A-Z]+[a-z]*"; // Will allow John but neither JOHN nor john



    @Override

    public boolean validate(ConstrainedView<String> view) {

        return view.getCurrentValue().matches(pattern);

    }



    @Override

    public int getMessageId() {

        return R.string.nameViolation;

    }

}



Step 2 - Register validator


You must register the validator by-name in order to be able to use it. The ValidationRegistry internally used in the BARACUS framework is a managed bean which registers the validators in a postConstruct method. But since this validator is a more or less invariant item, we will add it to the context after the context has been built. This is necessary since the ValidationFactory has not been setup in the Context-initialization phase.

In a prior tutorial we have created an after-context-init trigger (AfterContextInitialized). This is the correct place to add the validator to the registry because in the ApplicationContext's init phase the internal ValidationFactory (which is also a bean) still is null.

So we add the validator to the context :

public class AfterContextInitialized implements ApplicationContextInitializer {

    ...

    @Override

    public void afterContextIsBuilt() {

        if (reinit) {

            ...

            ApplicationContext.registerValidator(new NameValidator());



        }

    }

}



Step 3 - Wire the validator


We are almost done. You simply have to wire the validator by-name inside the desired layout xml file. In our case, we want to have this validator added to the existing validator in the customer_editor.xml :

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

              xmlns:app="http://schemas.android.com/apk/res-auto"

              android:orientation="vertical"

              android:layout_width="match_parent"

              android:layout_height="match_parent">

    ...

    <net.mantucon.baracus.ui.ConstrainedEditText android:id="@+id/txtCustomerFirstName"

                                                 android:layout_width="fill_parent"

                                                 android:layout_height="wrap_content"

                                                 app:validatedBy="stringNotEmpty,nameValidator"

            />

    ...

    <net.mantucon.baracus.ui.ConstrainedEditText android:id="@+id/txtCustomerLastName"

                                                 android:layout_width="fill_parent"

                                                 android:layout_height="wrap_content"

                                                 app:validatedBy="stringNotEmpty,nameValidator"

            />

    ...

</LinearLayout>


The validators are applied in the direction you add them to the list.

Conclusion


Writing custom validators is easy. They make the app design more easy and efficient and allow you to have very specific rules beeing applied to user's form data. But that's not all. You even can have more sophisticated constraints; since you can access any bean through your ApplicationContest statically you even can have database driven checks.

Next Tutorial : Advanced Logging (coming up)