Thursday, March 11, 2010

Eclipse: Using extension-point to avoid cyclic dependency between 2 plugins without refactoring code


I have encountered a cyclic dependency issue while developing application using eclipse based plug-in development environment. Here I am going to explain the issue, which you might be also encountered during the plug-in development and which would have lead you to re-factorize your code to solve this issue. But here I am going to use extension-point using which I can invoke execute code from another plug-in and get results without adding interdependency between them.

Example:
I have eclipse plug-ins PluginA and PluginB, here PluginB is already dependent on PluginA. Now I have some class method in PluginB, and I would like to execute that method from one of the class in PluginA .
- If we add the dependency of PluginA on PluginB then it would result in cyclic dependency.
- Alternatively, what we can do is to re-factor the code in such a way that this method and relevant calsses moved to PluginA so that it would be available in both PluginA and PluginB, but this solution will be expensive in the well deployed system since it involves lot of testing efforts and it may break, if we missed out something during re-factoring, And there may more such logic should come out and again we need to re-factorize with same process.
- Here, I am going to discuss alternate and permanent solution which can be used for calling method of the class which belong to PluginB form some class of PluginA without adding dependency and avoiding the re-factorization of the code.


Solution

Step 1: Create extension point.

  • Create new common plug-in let's say PluginC.
  • In this common plug-in create on interface as follows:
    package test;
    
    import java.util.Map;
    
    /**
     * @author Tejash Shah
     *
     */
    public interface IActionHandler {
      /**
         * This method is used by  ActionManager class to set the id when contribution is added in plugin.xml
         * 
         * @param id
         */
     public void setId(String id);
     
     /**
         * @return the unique id of the Action Handler
         */
        public String getId();
        
        /**
         * @param variables
         *            Here, you can pass the set of Objects which is required by the Action Handler to run the action. Here
         *            key will be name of variable and value can be any Object containing value
         * @return the result of the execution, which can be any kind of Object
         */
        public Object run(Map variables);
    }
    
  • Now open plugin.xml for PluginC using Plug-In Editor of eclipse, switch to "Extension Points" tab and click on "Add" button to add the extension point

  • Provide the "Extension Point ID", "Extension Point Name" and "Extension Point Schema" as per your requirements.
  • On "Finish" it will open editor for defining "Extension Point" schema as follows, switch to "Definition" tab in it

  • Click on "New Element" to add additional element bellow, and provide its Name as "ActionHandler"

  • Then select "ActionHandler" element and add 2  "New Attribute" named "id" and "handlerClass" for the same and set attribute details as follows:

  • For "id" element select value of "Use" as "requied" and rest of them keep it as it is
  • For "handlerClass" select the value of "Use" as "requied", then set "Type" to "java" and for value of "Implements" click on "Browse..." button and select the interface IActionHandler which we defined previously.
  • After this select "extension" element in the tree and add "New Sequence" and select check box for "Unbounded" for the newly added sequence.

  • Then right click on the "Sequence (1-*)" and form context menu select "New" -> "ActionHandler" and for "ActionHAndler" also select check box for the "Unbounded".
  • Now, "Save All" the edited files (plugin.xml and schema file).
  • If you check the source of the schema (.exsd) file for this extension-point, it would be as follows:

    <?xml version='1.0' encoding='UTF-8'?>
    <!-- Schema file written by PDE -->
    <schema targetNamespace="Test" xmlns="http://www.w3.org/2001/XMLSchema">
    <annotation>
          <appinfo>
             <meta.schema plugin="Test" id="test.actionHandler" name="SampleActionHandler"/>
          </appinfo>
          <documentation>
             [Enter description of this extension point.]
          </documentation>
       </annotation>
    
       <element name="extension">
          <annotation>
             <appinfo>
                <meta.element />
             </appinfo>
          </annotation>
          <complexType>
             <sequence minOccurs="1" maxOccurs="unbounded">
                <element ref="ActionHandler" minOccurs="1" maxOccurs="unbounded"/>
             </sequence>
             <attribute name="point" type="string" use="required">
                <annotation>
                   <documentation>
                      
                   </documentation>
                </annotation>
             </attribute>
             <attribute name="id" type="string">
                <annotation>
                   <documentation>
                      
                   </documentation>
                </annotation>
             </attribute>
             <attribute name="name" type="string">
                <annotation>
                   <documentation>
                      
                   </documentation>
                   <appinfo>
                      <meta.attribute translatable="true"/>
                   </appinfo>
                </annotation>
             </attribute>
          </complexType>
       </element>
    
       <element name="ActionHandler">
          <complexType>
             <attribute name="id" type="string" use="required">
                <annotation>
                   <documentation>
                      
                   </documentation>
                </annotation>
             </attribute>
             <attribute name="handlerClass" type="string" use="required">
                <annotation>
                   <documentation>
                      The class which will provide implementation of IActionHandler and where you can write the logic to handle the action.
    
    You can it&apos;s instance by providing id of ActionHandler and you can call run() method to excute the action
                   </documentation>
                   <appinfo>
                      <meta.attribute kind="java" basedOn=":test.IActionHandler"/>
                   </appinfo>
                </annotation>
             </attribute>
          </complexType>
       </element>
    
       <annotation>
          <appinfo>
             <meta.section type="since"/>
          </appinfo>
          <documentation>
             [Enter the first release in which this extension point appears.]
          </documentation>
       </annotation>
    
       <annotation>
          <appinfo>
             <meta.section type="examples"/>
          </appinfo>
          <documentation>
             [Enter extension point usage example here.]
          </documentation>
       </annotation>
    
       <annotation>
          <appinfo>
             <meta.section type="apiinfo"/>
          </appinfo>
          <documentation>
             [Enter API information here.]
          </documentation>
       </annotation>
    
       <annotation>
          <appinfo>
             <meta.section type="implementation"/>
          </appinfo>
          <documentation>
             [Enter information about supplied implementation of this extension point.]
          </documentation>
       </annotation>
    
    
    </schema>
    
Step 2: Create a manager class withing PluginC 
  •  Create new class "SampleActionManager" withing PluginC as follows to load the all contributed actionHandlers.

    /**
     * 
     */
    package test;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.eclipse.core.runtime.CoreException;
    import org.eclipse.core.runtime.IConfigurationElement;
    import org.eclipse.core.runtime.IExtensionPoint;
    import org.eclipse.core.runtime.IExtensionRegistry;
    import org.eclipse.core.runtime.Platform;
    
    /**
     * @author Tejash Shah
     *
     */
    public class SampleActionManager {
    
      private static SampleActionManager handler = new SampleActionManager();
         
         private static final String ATTRIB_ID = "id";
    
         private static final String ATTRIB_CLASS = "handlerClass";
         
         private static final String SAMPEL_ACTION_HANDLER = "test.actionHandler"; // ID given in Plugin.xml while adding extension point
    
         private Map actionHandlers;
    
         private SampleActionManager() {
             loadSonicActionHandler();
         }
    
         private void loadSonicActionHandler() {
             actionHandlers = new HashMap();
             IConfigurationElement[] configurationElements = getConfigurationElements(SAMPEL_ACTION_HANDLER);
             for (int i = 0; i < configurationElements.length; i++) {
                 try {
                     Object extensionObject = configurationElements[i].createExecutableExtension(ATTRIB_CLASS);
                     IActionHandler contributor = (IActionHandler)extensionObject;
                     String id = configurationElements[i].getAttribute(ATTRIB_ID);
                     contributor.setId(id);
                     actionHandlers.put(id, contributor);
                 } catch (CoreException e) {
                     e.printStackTrace();
                 }
             }
         }
    
         public static SampleActionManager getInstance() {
             return handler;
         }
    
         public IActionHandler getActionHandlerbyId(String id) {
             return actionHandlers.get(id);
         }
         
         public static IConfigurationElement[] getConfigurationElements(String extensionPointID) {
             IExtensionRegistry registry = Platform.getExtensionRegistry();
             IExtensionPoint contentTypesXP = registry.getExtensionPoint(extensionPointID);
             if (contentTypesXP == null)
                 return new IConfigurationElement[0];
             IConfigurationElement[] allContentTypeCEs = contentTypesXP.getConfigurationElements();
             return allContentTypeCEs;
         }
    }
    

  • You can also optionally create an abstract class "AbstractActionHandler" as follows:



    /**
     * 
     */
    package test;
    
    
    /**
     * @author Tejash
     *
     */
    public abstract class AbstractActionHandler implements IActionHandler {
    
     private String id = "";
      
     /* (non-Javadoc)
      * @see test.IActionHandler#getId()
      */
     public String getId() {
      return this.id;
     }
    
     /* (non-Javadoc)
      * @see test.IActionHandler#setId(java.lang.String)
      */
     public void setId(String id) {
      this.id = id;
     }
    
    }
    

    Step 3: Add plug-in dependencies.
    • Open plugin.xml for the PluginA and PluginB in "Plug-In Editor" switch to "Dependencies" tab and add dependency to PlugInC in both the plug-in and save both plugin.xml
    Step 4: Add contribute to this extension point form PluginB where the particular method logic belongs to
    • Open plugin.xml for the PluginB in "Plug-in Editor" and go to "Extensions" tab, click on "Add" button and add the extension for "test.actionHandler" which we have defined in PluginC.
    • You can provide "ID" and "Name" for this extension as shown in the bellow image.

    • Then select the child node of Extension to provide extension related details as follows:

    • e.g. I have given "id" as "MyActionHandler", then click on "handlerClass" link which will launch "New Java Class Wizard". Here you can provide all required details as shown in the above image. And optionally select Sueprclass value as the "AbstractActionHandler"Class if you have created it previously.
    • On click on "Finish" it will create default implementation of "MyActionHandler" class, you change run method to invoke any method of class which belong to PluginB. view the sample code as follows:

      /**
       * 
       */
      package test;
      
      import java.util.Map;
      
      /**
       * @author tejash
       *
       */
      public class MyActionHandler extends AbstractActionHandler implements
        IActionHandler {
      
       /**
        * 
        */
       public MyActionHandler() {
        // TODO Auto-generated constructor stub
       }
      
       /* (non-Javadoc)
        * @see test.IActionHandler#run(java.util.Map)
        */
       public Object run(Map variables) {
        // You logic to invoke method particular class goes here
        //e.g. 
        // return Myclass.myMethod();
        // If you have some arguments then you can receive as part of variables object 
        return null;
       }
      
      }
      
    Step 5: Finally use ActionManger from PluginA to call desired class method which belongs to PluginA.
    • Now in the PluginA where we wanted to make call to some method of PlugInB, we can get the ActionHandler object for the same and then pass appropriate variable and invoke run method to get the result object as follows:

      IActionHandler actionHandler = SampleActionManager.getInstance().getActionHandlerbyId("MyActionHandler");
        Map variables = new HashMap();
        //Here you can add as many objects to variables
        //then make call to run method to execute method in another plug-in and get results as follows:
        Object result = actionHandler.run(variables);
        //use the result here for further processing logic
      
    • Once you get the result Object, you can write further processing logic on it

    You are now able to invoke a class method of PluginB from PluginA without adding dependency or code re-factoring.

    Note: Next time if you code across similar issue for other class methods even if they belong to some other plugin say PluginX then also you can use the same "Extension" by just adding dependent on PluginC. Then create ActionHandler using Extension.

    Share |

    How to avoid Spring validation errors: Unable to locate Spring Namespace handler , Incorrect usage of element for certain elements , No setter found for property , Class Not Found


    I integrated spring IDE plug-ins in my eclipse environment. I wanted use some additional namespaces like camel, CXF and some custom schema to spring.xml, spring beans file. When I tried to validate the spring.xml file the spring IDE it was showing following errors and warnings in "Problems" / "Error Log" view of eclipse

    •  Unable to locate Spring Namespace handler...
    •  Incorrect usage of element for certain elements ... (The element was refereed form my custom schema)
    •  Many errors of Class Not Found.
    •  No setter found for property ... (When I am trying to set some property in spring.xml) 

    Here, I am going to discuss the approaches I used to solve these issues.


    Issue 1: Unable to locate Spring Namespace handler...
    1) To resolve this issue there must be Namespace Handler implemented for the namespaces used in the spring beans (spring.xml) file. Now add the jar file containing NamespaceHandler implementation and all other dependent jars to the spring project's class path. This would probably solve this issue, but still this error is not resolved for me. In this case I have applied second solution of contributing NamespaceHandler using eclipse plug-in as follows:.
    - You can create new eclipse plug-in or use any existing eclipse plug-in for this purpose
    - First copy jar file containing NamespaceHandler class and all other dependent jar files to the plug-in. (e.g. I copied them to "lib" directory of my plug-in)
    - Now open plugin.xml using plug-in editor in the eclipse and go to "Runtime" tab. Here in the "Classpath" section add all the jars which we have copied. (Before this make sure that .classpath file is not read-only)
    - This will add all the jars in to project as well as plug-in's run-time classpath.
    - Then configure plug-in dependencies by going to "Dependencies" tab. Here add the dependencies to following plug-ins if they are not already added.
    • org.springframework.ide.eclipse.beans.core
    • org.eclipse.ui
    • org.eclipse.core.runtime

    - Note: you can also add the more plugin dependencies if they are required for the NamespaceHandler to work.
    - After this you can contribute to spring NamespaceHandler using extension; you can switch to "Extensions" tab for this.
    - Here, add the extension "org.springframework.ide.eclipse.beans.core.namespaces" by clicking on the add button. This will create extension entry as shown in the following image.
    - If you look at source of plugin.xml file it would look like:

    <extension
            point="org.springframework.ide.eclipse.beans.core.namespaces">
         <namespace
               namespaceHandler="spring.util.NamespaceHandler1"
               uri="spring.util.namespace1">
         </namespace>
      </extension>
    - As shown in above screen-shot we need to provide actual namespace URI as value of "uri", the fully qualified class name of NamespaceHandler class as value if "namespaceHandler". You can give some meaningful "name" (optional) to this contribution. If you have your custom element provider then provide its fully qualified name in "elementProvider" (optional).
    - If you have more than one custom namespaces refereed in spring.xml then you can either add multiple extension-point contribution as shown above or you can just add multiple namespace entries withing same extension-point contribution as follows:
    • Right click on extension-point contribution to open the context menu; click on new -> namespace. This will add the new NamespaceHandler contribution entry and configure required required values as I have mentioned above.

    - Now launch the eclipse with this plug-in contribution and it would resolve all warnings related to Unable to locate Spring Namespace handler...

     Issue 2 : Incorrect usage of element for certain elements ... (The element was refereed form my custom schema)
    If you have contributed NamespaceHandler as shown in the above solution and your namespace handler properly handles the validation for the different element of the custom schema, then this issue will also be resolved as part of it.

    Issue 3: Many errors of Class Not Found.
    This error can come in the "Problems" view or in the "Error Log" view you might find stack trace stating ClassNotFoundExceotion or NoClassDefError. There are 2 scenarios when this error could come
    1. NamespaceHandler class is directly or indirectly referring that particular class, and it is not part of plug-in (The plug-in using which we contributed NamespaceHandlers) classpath.
      • This you can determine by looking at the stack trace of that error. If stack trace some where refers to Namespace handler or its relevant classes then the particular class is referred directly or indirectly from Namespace handler.
      • In this case pick up the jar file containing missing class in it and copy it to plug-in (e.g. "lib" directory of plug-in) and then add it to runtime classpath of the plug-in as I have mentioned in the Issue-1 and this would get rid of particular class not found error
    2. Bean defined inside spring.xml  file is referring, that class is missing or it is internally using this class.
      • Here you check the error in "Problems" view and check if this class is used to create the bean in side spring,xml or not? or else this class is referred in directly from one of the beans defined in the spring.xml
      • To solve this issue we need to determine the required jars for the all missing classes and then add all the required jar to Spring project's class path. (Here it is not required to copy jar files to project it self you can refer them as external jars also)
    Issue 4: No setter found for property ... (When I am trying to set some property in spring.xml)
    This error comes in "Problems" view of the eclipse. It could have occurred because of 2 reasons
    1. The property which we are trying to set on the bean, for the same setter method was not defined in the class.
      • In this case define setter method withing class definition as "setPropertyName(...)" with required logic.
    2. The setter method for that particular property does not belongs to same class but it belongs to parent or super parent class, but still you are getting "No setter found for property ..." error for the same property
      • In this scenario, you need to verify that all the classes in the hierarchy and all the class which it may directly or indirectly refer to must be present in "Spring" project's class path. (if they are present in third-party jars then those jars should be part of "Spring" project's classpath).

    Share |

    Thursday, March 04, 2010

    Embedding a Wave in Blogger various ways


    I came across the wave created by Timothy Brown. Which talks about various ways to embed a Wave to Blogger. Hence I feel that I should embed it here so that everyone can get benefited form it.

    Share |

    Topics

     
    Embed Wave to Blogger