APAM/APAM versus OSGi

From Wiki Adele Team
< APAM
Revision as of 10:49, 3 December 2012 by Mehdi (talk | contribs)

Jump to: navigation, search

Overview

OSGi and APAM are both platforms for service oriented java applications, but their similarities end up there. The way of implementing, using and naming are done differently, so you should pay attention to those details so you don't mess up the informations on your mind. If you need more informations about how OSGi works, you should check here and here. For APAM information and updates you can check here.

Contrast Example

In order to give you a good contrast between OSGi and APAM and pin point the changes, we will pickup an example available here and convert it to APAM, doing so we should be able to see the difference between those two frameworks more clearly with hands on it. This example is implemented by, and for, OSGi. It is called spellchecker, at this point we will work in the Example7.

For better understanding it is important to define the concept of what a 'service' mean to us. On real life Service is something that you need but do not know how to do it yourself (generally), for that you look for someone that can provide this service for you. In software, the concept is pretty much the same: a service is when you need something to be done, but you do not know how it is done neither who can do it, and sometimes neither who to ask it for.

When you ask for a service you do not care about the details, you just want things to be done [figure] no matter who or how its done, that is where APAM acts. So, keep that in mind : service is something we want but we do not know where to find or how it is done. Now, we can continue with the spellchecker example explanation.

How would it be in real life?

In order to implement the spell checker, it needs someone to tell him what is right and wrong, the spell checker only know the setence to be verified. In order to verify this information it uses a dictionary service, verifying if one word of the sentence exists in a dictionary. Thus, the dictionary provides a complete set of the valid words.

An user story of the spellchecker application can be: someone enters the sentence «APAM is awsome! «  in a client application, this application sends the informations to a service (the spellchecker) and the user of the client application should be aware of which word do not exist in a dictionary, at this point we do not care about which dictionary we are talking about, can be any – english, french, etc.

Spellchecker example highlevel dependency diagram

As we can see, the client application does not know anything about the spellchecker, he only knows an interface that has one signature that gives him the hability to spell check one sentence. The client application have no idea how instantiate it or even if its available. After sending the information to the service, the service should point us out which word is incorrect.

The code

Example 3

Enough of talking, lets take a look on the osgi client application code:

public class Activator implements BundleActivator
{
    public void start(BundleContext context) throws Exception
    {
        // Query for all service references matching any language.
        ServiceReference[] refs = context.getServiceReferences(
            DictionaryService.class.getName(), "(Language=*)");

        if (refs != null)
        {
            try
            {
                System.out.println("Enter a blank line to exit.");
                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                String word = "";

                // Loop endlessly.
                while (true)
                {
                    // Ask the user to enter a word.
                    System.out.print("Enter word: ");
                    word = in.readLine();

                    // If the user entered a blank line, then
                    // exit the loop.
                    if (word.length() == 0)
                    {
                        break;
                    }

                    // First, get a dictionary service and then check
                    // if the word is correct.
                    DictionaryService dictionary =
                        (DictionaryService) context.getService(refs[0]);
                    if (dictionary.checkWord(word))
                    {
                        System.out.println("Correct.");
                    }
                    else
                    {
                        System.out.println("Incorrect.");
                    }

                    // Unget the dictionary service.
                    context.ungetService(refs[0]);
                }
            } catch (IOException ex) { }
        }
        else
        {
            System.out.println("Couldn't find any dictionary service...");
        }
    }

    /**
     * Implements BundleActivator.stop(). Does nothing since
     * the framework will automatically unget any used services.
     * @param context the framework context for the bundle.
    **/
    public void stop(BundleContext context)
    {
        // NOTE: The service is automatically released.
    }
}

After adapting this code to ApAM it would look like this:

public class Activator 
{
    private DictionaryService dictionary;
    public void start() throws Exception
    {

            try
            {
                System.out.println("Enter a blank line to exit.");
                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                String word = "";

                // Loop endlessly.
                while (true)
                {
                    // Ask the user to enter a word.
                    System.out.print("Enter word: ");
                    word = in.readLine();

                    // If the user entered a blank line, then
                    // exit the loop.
                    if (word.length() == 0)
                    {
                        break;
                    }
                    // First, get a dictionary service and then check
                    // if the word is correct.
                    if (dictionary.checkWord(word))
                    {
                        System.out.println("Correct.");
                    }
                    else
                    {
                        System.out.println("Incorrect.");
                    }
                }
            } catch (IOException ex) { }       
    }
}

And this is the diff or those two implementations:


Vimdiff of the files

 1 public class Activator implements BundleActivator
 2 {
 3
 4     public void start(BundleContext context) throws Exception
   ------
   ------
 5     {
 6         ServiceReference[] refs = context.getServiceReferences(
 7             DictionaryService.class.getName(), "(Language=*)");
 8
 9         if (refs != null)
10         {
11             try
12             {
13                 System.out.println("Enter a blank line to exit.");
14                 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
15                 String word = "";
16 +-- 11 lignes : Loop endlessly.
27                     {
28                         break;
29                     }
30
31                     // First, get a dictionary service and then check
32                     // if the word is correct.
33                     DictionaryService dictionary =
34                         (DictionaryService) context.getService(refs[0]);
   ------
35                     if (dictionary.checkWord(word))
36                     {
37                         System.out.println("Correct.");
38                     }
39                     else
40                     {
41                         System.out.println("Incorrect.");
42                     }
43
44                     // Unget the dictionary service.
45                     context.ungetService(refs[0]);
46                 }
47             } catch (IOException ex) { }
48         }
49         else
50         {
51             System.out.println("Couldn't find any dictionary service...");
52         }
53     }
54
55     /**
56      * Implements BundleActivator.stop(). Does nothing since
57      * the framework will automatically unget any used services.
58      * @param context the framework context for the bundle.
59     **/
60     public void stop(BundleContext context)
61     {
62         // NOTE: The service is automatically released.
63     }
64 }

As we can see, apam simplified a little more the way of using services, by :

  1. Removing the implementation of BundleActivator interface, avoiding adding dummie implementation that we are not always aware of what to put inside
  2. Avoid acquiring manually the existing services
  3. Avoid handling manually our own state based on other service availability
  4. Avoid manual releasing of the service

So the only thing we need to do is to declare a class attribute (which is an interface – just the contract of a class without the know how to implement the task), the APAM handles the laborious task of finding someone who implements this contract and attachs an instance in your class attribute during runtime.

But this is not exactly where the magic happens, under the hood (on the service implementation) is where APAM becomes an eyecandy.

Let's put aside for a moment the client perspective, and work a little as a provider by implementing the Dictionary service for instance. In the website this refers to the Example 2. Below we can see the difference between the OSGi and APAM service implementation more clearly.

[show diff file of the code]

Here you can see we removed part of the old code, mainly :

  1. Avoiding implementing dummie methods
  2. Removing code for service initialization (bundle activator implementation)

The killer application is not the simplification of OSGi code, but we are going to see further more details of what can be done with APAM.

Example 6

On the Example 6 its implemented a spell checker service that keeps an updated list of all available dictionary services. Recall that one spell checker service can use one among many dictionary service available, it works like having several dictionaries available, but you do not know which language is which.

The first code you will see is the original osgi implementation:

public class Activator implements BundleActivator, ServiceListener
{
    // Bundle's context.
    private BundleContext m_context = null;
    // List of available dictionary service references.
    private ArrayList m_refList = new ArrayList();
    // Maps service references to service objects.
    private HashMap m_refToObjMap = new HashMap();
    // The spell checker service registration.
    private ServiceRegistration m_reg = null;

    public void start(BundleContext context) throws Exception
    {
        m_context = context;

        synchronized (m_refList)
        {
            // Listen for events pertaining to dictionary services.
            m_context.addServiceListener(this,
                "(&(objectClass=" + DictionaryService.class.getName() + ")" +
                "(Language=*))");

            // Query for all dictionary services.
            ServiceReference[] refs = m_context.getServiceReferences(
                DictionaryService.class.getName(), "(Language=*)");

            // Add any dictionaries to the service reference list.
            if (refs != null)
            {
                for (int i = 0; i < refs.length; i++)
                {
                    // Get the service object.
                    Object service = m_context.getService(refs[i]);

                    // Make that the service is not being duplicated.
                    if ((service != null) &&
                        (m_refToObjMap.get(refs[i]) == null))
                    {
                        // Add to the reference list.
                        m_refList.add(refs[i]);
                        // Map reference to service object for easy look up.
                        m_refToObjMap.put(refs[i], service);
                    }
                }

                // Register spell checker service if there are any
                // dictionary services.
                if (m_refList.size() > 0)
                {
                    m_reg = m_context.registerService(
                        SpellChecker.class.getName(),
                        new SpellCheckerImpl(), null);
                }
            }
        }
    }

    public void stop(BundleContext context)
    {
        // NOTE: The services automatically released.
    }

    public void serviceChanged(ServiceEvent event)
    {
        synchronized (m_refList)
        {
            // Add the new dictionary service to the service list.
            if (event.getType() == ServiceEvent.REGISTERED)
            {
                // Get the service object.
                Object service = m_context.getService(event.getServiceReference());

                // Make that the service is not being duplicated.
                if ((service != null) &&
                    (m_refToObjMap.get(event.getServiceReference()) == null))
                {
                    // Add to the reference list.
                    m_refList.add(event.getServiceReference());
                    // Map reference to service object for easy look up.
                    m_refToObjMap.put(event.getServiceReference(), service);

                    // Register spell checker service if necessary.
                    if (m_reg == null)
                    {
                        m_reg = m_context.registerService(
                            SpellChecker.class.getName(),
                            new SpellCheckerImpl(), null);
                    }
                }
                else if (service != null)
                {
                    m_context.ungetService(event.getServiceReference());
                }
            }
            // Remove the departing service from the service list.
            else if (event.getType() == ServiceEvent.UNREGISTERING)
            {
                // Make sure the service is in the list.
                if (m_refToObjMap.get(event.getServiceReference()) != null)
                {
                    // Unget the service object.
                    m_context.ungetService(event.getServiceReference());
                    // Remove service reference.
                    m_refList.remove(event.getServiceReference());
                    // Remove service reference from map.
                    m_refToObjMap.remove(event.getServiceReference());

                    // If there are no more dictionary services,
                    // then unregister spell checker service.
                    if (m_refList.size() == 0)
                    {
                        m_reg.unregister();
                        m_reg = null;
                    }
                }
            }
        }
    }

    private class SpellCheckerImpl implements SpellChecker
    {
        /**
         * Implements SpellChecker.check(). Checks the
         * given passage for misspelled words.
         * @param passage the passage to spell check.
         * @return An array of misspelled words or null if no
         *         words are misspelled.
        **/
        public String[] check(String passage)
        {
            // No misspelled words for an empty string.
            if ((passage == null) || (passage.length() == 0))
            {
                return null;
            }

            ArrayList errorList = new ArrayList();

            // Tokenize the passage using spaces and punctionation.
            StringTokenizer st = new StringTokenizer(passage, " ,.!?;:");

            // Lock the service list.
            synchronized (m_refList)
            {
                // Loop through each word in the passage.
                while (st.hasMoreTokens())
                {
                    String word = st.nextToken();

                    boolean correct = false;

                    // Check each available dictionary for the current word.
                    for (int i = 0; (!correct) && (i < m_refList.size()); i++)
                    {
                        DictionaryService dictionary =
                            (DictionaryService) m_refToObjMap.get(m_refList.get(i));

                        if (dictionary.checkWord(word))
                        {
                            correct = true;
                        }
                    }

                    // If the word is not correct, then add it
                    // to the incorrect word list.
                    if (!correct)
                    {
                        errorList.add(word);
                    }
                }
            }

            // Return null if no words are incorrect.
            if (errorList.size() == 0)
            {
                return null;
            }

            // Return the array of incorrect words.
            return (String[]) errorList.toArray(new String[errorList.size()]);
        }
    }
}

In ApAM, we can inject a field of the type Set, and automatically ApAM will keep it up to date with all available services. Thus, the only implementation required in ApAM to perform the same task as the code given above.

The ApAM version of the same code:

public class APAMTutorialExample6 implements SpellChecker {
	// List of available dictionary service references.
	private Set<DictionaryService> m_refList;

	/**
	 * Implements SpellChecker.check(). Checks the given passage for misspelled
	 * words.
	 * 
	 * @param passage
	 *            the passage to spell check.
	 * @return An array of misspelled words or null if no words are misspelled.
	 **/
	public String[] check(String passage) {
		// No misspelled words for an empty string.
		if ((passage == null) || (passage.length() == 0)) {
			return null;
		}

		ArrayList errorList = new ArrayList();

		// Tokenize the passage using spaces and punctionation.
		StringTokenizer st = new StringTokenizer(passage, " ,.!?;:");

		// Lock the service list.
		synchronized (m_refList) {
			// Loop through each word in the passage.
			while (st.hasMoreTokens()) {
				String word = st.nextToken();

				boolean correct = false;

				// Check each available dictionary for the current word.
				for (DictionaryService dictionary:m_refList){

					if (dictionary.checkWord(word)) {
						correct = true;
					}
				}

				// If the word is not correct, then add it
				// to the incorrect word list.
				if (!correct) {
					errorList.add(word);
				}
			}
		}

		// Return null if no words are incorrect.
		if (errorList.size() == 0) {
			return null;
		}

		// Return the array of incorrect words.
		return (String[]) errorList.toArray(new String[errorList.size()]);
	}
	
	public void start(){
		System.out.println("Starting spellchecker service");
	}
	
}

As we can see, the largest portion of the code was removed. This portion use to handle the arrival and departure of new service providers, this handle was necessary to update the list of available providers. In ApAM that is no longer necessary, ApAM takes care of keep this list up to date for you.

Example 7

On the Example 7, a spellchecker client is implemented. This implementation reads an entry from the user in form of a sentence, after this step the implementation verifies the availability of a service called spellchecker. If such service is available it sends the sentence (filled by the user) to this service and gets in return the words that were not find in the dictionaries, remember that internally the spellcheck implementation - Example 6 - looks in the available Dictionary services the words used in the sentence, if it does not exist in any of them it assumes that such word does not exist, thus, it is considered as misspelled.

Below is the original OSGi implementation of this client (the original one):

public class Activator implements BundleActivator
{
    // Bundle's context.
    private BundleContext m_context = null;
    // The service tacker object.
    private ServiceTracker m_tracker = null;

    public void start(BundleContext context) throws Exception
    {
        m_context = context;

        // Create a service tracker to monitor dictionary services.
        m_tracker = new ServiceTracker(
            m_context,
            m_context.createFilter(
                "(objectClass=" + SpellChecker.class.getName() + ")"),
            null);
        m_tracker.open();

        try
        {
            System.out.println("Enter a blank line to exit.");
            String passage = "";
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

            // Loop endlessly.
            while (true)
            {
                // Ask the user to enter a passage.
                System.out.print("Enter passage: ");
                passage = in.readLine();

                // Get the selected dictionary service, if available.
                SpellChecker checker = (SpellChecker) m_tracker.getService();

                // If the user entered a blank line, then
                // exit the loop.
                if (passage.length() == 0)
                {
                    break;
                }
                // If there is no spell checker, then say so.
                else if (checker == null)
                {
                    System.out.println("No spell checker available.");
                }
                // Otherwise check passage and print misspelled words.
                else
                {
                    String[] errors = checker.check(passage);

                    if (errors == null)
                    {
                        System.out.println("Passage is correct.");
                    }
                    else
                    {
                        System.out.println("Incorrect word(s):");
                        for (int i = 0; i < errors.length; i++)
                        {
                            System.out.println("    " + errors[i]);
                        }
                    }
                }
            }
        } catch (Exception ex) { }
    }

    /**
     * Implements BundleActivator.stop(). Does nothing since
     * the framework will automatically unget any used services.
     * @param context the framework context for the bundle.
    **/
    public void stop(BundleContext context)
    {
    }
}

Below is shown the implementation of the very same client using ApAM instead. The major change is the elimination of all the excerpt of code dedicated to fill up some metadata information required to fetch an instance of the required service. ApAM implementation uses a mnemonic fashion of doing it, reaching the same goals.

public class APAMTutorialExample7 {

	apam.tutorial.service.SpellChecker checker;
	
    public void start() throws Exception
    {
       
        // Create a service tracker to monitor dictionary services.
      
        try
        {
            System.out.println("Enter a blank line to exit.");
            String passage = "";
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

            // Loop endlessly.
            while (true)
            {
                // Ask the user to enter a passage.
                System.out.print("Enter passage: ");
                passage = in.readLine();

                // If the user entered a blank line, then
                // exit the loop.
                if (passage.length() == 0)
                {
                    break;
                }
                // If there is no spell checker, then say so.
                else if (checker == null)
                {
                    System.out.println("No spell checker available.");
                }
                // Otherwise check passage and print misspelled words.
                else
                {
                    String[] errors = checker.check(passage);

                    if (errors == null)
                    {
                        System.out.println("Passage is correct.");
                    }
                    else
                    {
                        System.out.println("Incorrect word(s):");
                        for (int i = 0; i < errors.length; i++)
                        {
                            System.out.println("    " + errors[i]);
                        }
                    }
                }
            }
        } catch (Exception ex) { }
    }


}

Try for yourself

If you wanna try yourself, you can download the APAM-Runtime and install the generated bundles.


Or if you wanna check the code and make the changes yourself, you can download the adapted example from here. The requirements to compile and run this example are:

  • java develoment toolkit (>= 1.5)
  • maven (>= 3.0)