Wednesday, December 29, 2010

Activating the EMF Edit ItemProvider AdapterFactory-s (with CDO)

I used to have a problem with CDO Editor UI: it does not use my EMF Edit models' ItemProvider implementation (like label formatting, image/icon, and so on).

Today I found the reason, I changed the model's nsURI and did not change all references to it.

The relevant portion is plugin.xml for your.model.edit plugin :

   <extension point="org.eclipse.emf.edit.itemProviderAdapterFactories">
      <factory
            uri="http://www.abispulsa.com/model/1.0"
            class="com.abispulsa.provider.AbispulsaItemProviderAdapterFactory"
            supportedTypes=
              "org.eclipse.emf.edit.provider.IEditingDomainItemProvider
               org.eclipse.emf.edit.provider.IStructuredItemContentProvider
               org.eclipse.emf.edit.provider.ITreeItemContentProvider
               org.eclipse.emf.edit.provider.IItemLabelProvider
               org.eclipse.emf.edit.provider.IItemPropertySource"/>
   </extension>

The way to get ComposedAdapterFactory I previously blogged turned out to be incorrect:

public class AbispulsaContentProvider extends AdapterFactoryContentProvider {

private static AdapterFactory adapterFactory;

static {
adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
new ResourceItemProviderAdapterFactory(),
new AbispulsaItemProviderAdapterFactory(),
new ReflectiveItemProviderAdapterFactory()
});
}

public AbispulsaContentProvider() {
super(adapterFactory);
}

}

The correct way is like this:

public class AbispulsaContentProvider extends AdapterFactoryContentProvider {

private static ComposedAdapterFactory adapterFactory;

static {
Registry registry = EMFEditPlugin.getComposedAdapterFactoryDescriptorRegistry();
adapterFactory = new ComposedAdapterFactory(registry);
adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
}

public AbispulsaContentProvider() {
super(adapterFactory);
}

}

Although then you should move the ComposedAdapterFactory creation to another singleton class so it can be reused from both ContentProvider and LabelProvider.

Note that for above to work, the extension org.eclipse.emf.edit.itemProviderAdapterFactories must also be set properly.

This is actually not CDO specific, but applies to all EMF Edit / Editor UI in general.
But it highlights a very important CDO feature: it reuses your EMF Edit ItemProvider implementations! :-)

Persisting EMF Objects to RDBMS with Teneo and Hibernate

In my project, EMF Objects are now being persisted in a CDO Model Repository. It used to use "plain Teneo", but before scraping the code I'd like to share it in my blog so in case I want to use Teneo directly again I won't be in trouble. ;-)

Teneo is a way to persist EMF Objects in a relational database using Hibernate and JPA annotations (you can also use EclipseLink, but I think Hibernate is better supported by Teneo).

JPA annotations are generated automatically by Teneo but you can configure the JPA mapping/annotations manually by using Teneo's JPA model annotations (not JPA Java annotations) on the EMF metamodel.

Connecting to the Hibernate EMF Data Store

private void initHibernate() {
// Set the database information, Environment is org.hibernate.cfg.Environment
final Properties props = new Properties();

// props.setProperty(Environment.DRIVER, "org.hsqldb.jdbcDriver");
// props.setProperty(Environment.USER, "sa");
// //props.setProperty(Environment.URL, "jdbc:hsqldb:file:/tmp/hsqldb");
// props.setProperty(Environment.URL, "jdbc:hsqldb:mem:abispulsamodel");
// props.setProperty(Environment.PASS, "");
// props.setProperty(Environment.DIALECT, org.hibernate.dialect.HSQLDialect.class.getName());
props.setProperty(Environment.DRIVER, "com.mysql.jdbc.Driver");
props.setProperty(Environment.USER, "root");
props.setProperty(Environment.URL, "jdbc:mysql://127.0.0.1:3306/abispulsamodel");
props.setProperty(Environment.PASS, "");
props.setProperty(Environment.DIALECT, MySQLInnoDBDialect.class.getName());
// Set cascade policy for containment and non-containment references
props.setProperty(PersistenceOptions.CASCADE_POLICY_ON_CONTAINMENT, "ALL");
props.setProperty(PersistenceOptions.CASCADE_POLICY_ON_NON_CONTAINMENT,
  "MERGE,PERSIST,REFRESH");

// Create and register the datastore, it will be accessible using
// this EMF Resource URI: hibernate://?dsname=abispulsa
hbds = HbHelper.INSTANCE.createRegisterDataStore("abispulsa");
// Set the EPackages that will be used with this data store
hbds.setEPackages(new EPackage[]{AbispulsaPackage.eINSTANCE});
hbds.setProperties(props);
hbds.initialize();
}

Note: the above syntax is for Teneo 1.1.2. For Teneo 1.2.0, setProperties() becomes setDataStoreProperties().

As you can see, configuring data store properties is just like with "plain" Hibernate.

You can change the DBMS backend easily by simply changing a few lines.
And of course you can externalize the configuration to a .properties file or .xml or whatever suits you best.

Using the Hibernate EMF Resource

Simple load:

HibernateResource resource = new HibernateResource(URI.createURI("hibernate://?dsname=abispulsa"));
resource.load(null);

Load with query, get specific EMF Object classes:
HibernateResource resource = new HibernateResource(
  URI.createURI("hibernate://?dsname=abispulsa&query1=FROM Organization"));
resource.load(null);

After you have a Resource, it's pretty much standard EMF / Resource stuff from here just like regular XMI-based resources.

"Copy and paste" (aka import) EMF Objects from an XMI file to Hibernate Resource:

ResourceSet rset = new ResourceSetImpl();
Resource xmiResource = rset.createResource(URI.createFileURI("/path/to/model/Catalog.xmi"));
try {
xmiResource.load(null);
log.info("XMI Resource has " + xmiResource.getContents().size() + " objects.");
for (EObject obj : xmiResource.getContents()) {
log.info(obj.toString());
}
Collection<EObject> copies = EcoreUtil.copyAll(xmiResource.getContents());
resource.getContents().addAll( copies );
resource.save(null);
} catch (Exception e) {
log.error("Error", e);
throw new RuntimeException(e);
}

Each time you open a Hibernate resource, a new Hibernate session is created by Teneo, which is fine for most purposes.

But you can change this by using your own session manager for Teneo.

Disconnecting from the Data Store

void closeHibernate() {
if (hbds != null) {
hbds.close();
HbHelper.INSTANCE.deRegisterDataStore(hbds);
hbds = null;
}
}

Comparison with CDO

With CDO it's almost the same, but with CDO there's a need to explicitly openTransaction() before editing objects in a CDO resource.

After manipulating a resource, either resource.save() or transaction.commit() will persist the changes.

Connect to CDO Server
private static final String CONNECTION_ADDRESS = "tcp://localhost:2036";
private static final String REPO_NAME = "abispulsabisnis";
...
log.info("Connecting to CDO to {} using repo {}", CONNECTION_ADDRESS, REPO_NAME);
org.eclipse.net4j.connector.IConnector connector = Net4jUtil.getConnector(IPluginContainer.INSTANCE, CONNECTION_ADDRESS);
// Create configuration
org.eclipse.emf.cdo.net4j.CDOSessionConfiguration sessionConfiguration = CDONet4jUtil.createSessionConfiguration();
sessionConfiguration.setConnector(connector);
sessionConfiguration.setRepositoryName(REPO_NAME);
org.eclipse.emf.cdo.session.CDOSession session = sessionConfiguration.openSession();
session.getPackageRegistry().putEPackage(AbispulsaPackage.eINSTANCE);

CDO is designed to be used over a network using TCP connector. But it's possible to run the CDO server and CDO client on the same VM instance, and a JVM Net4j connector is provided.

Open a Resource
org.eclipse.emf.cdo.transaction.CDOTransaction transaction = session.openTransaction();
org.eclipse.emf.cdo.eresource.CDOResource resource = transaction.getResource("/organization");

Save Changes
try {
//resource.save(null); // You can also do this (non-CDO aware)
transaction.commit(); // or this (CDO-aware)
} catch (Exception e) {
...
}

Update: As CDO committer Eike Stepper points out, CDO's resource.save() simply delegates to the underlying CDO Transaction's commit().

Close CDO Session
session.close();

That's it! I originally wanted to cover only Teneo but you got CDO Crash Course as a bonus. ;-)

Building a Workspace Resources-powered Common Navigator Framework (CNF) View

Common Navigator Framework (CNF) View is a very useful and flexible component to display structure of any object in an Eclipse RCP Application.

There are two ways to use CNF :

  1. Displaying Eclipse Resources inside Workspace
  2. Displaying custom content (non-resources)
The good news is the two ways can be mixed.
If you use Resources you can extend it with "custom content" underneath (using Navigator Content Extensions aka NCEs).

The bad news is even on the current milestone of Eclipse Indigo 3.7 it's still not straightforward to use CNF with Resources.
Again the good news is that there is a solution. :-)

I'll focus on displaying workspace resources (folders and files inside the workspace physical directory) in CNF because it's not obvious how to do it inside Eclipse RCP.
Note that if you use the IDE Application (at least the IDE WorkbenchAdvisor) it just works by default. But in Eclipse RCP you have to do some preparatory work.

The steps are outlined in Eclipse Help > Common Navigator Framework > Content and Action Binding, as follows.

Please note, that using CNF inside of your own RCP requires three additional steps.

  1. Add the
org.eclipse.ui.ide as dependent plug-in
  • To get the resource workspace root (
  • IWorkspaceRoot) as default page input, override the WorkbenchAdvisor.getDefaultPageInput() method in your WorkbenchAdvisor:
    public IAdaptable getDefaultPageInput()         {                 return ResourcesPlugin.getWorkspace().getRoot();         }
  • Hook the workbench adapters correctly before they are used, so add this code to the
  • WorkbenchAdvisor.initialize(IWorkbenchConfigurer) method:
    public void initialize(IWorkbenchConfigurer configurer)         {                 IDE.registerAdapters();         }

    This will display workspace resources with labels but there are no icons yet.

    To display icons you still need to "steal" the internal declareWorkbenchImages() from org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java in org.eclipse.ide.ui.application plugin.

    So the complete relevant parts become like this:

    import org.eclipse.core.resources.IWorkspaceRoot;
    import org.eclipse.core.resources.ResourcesPlugin;
    import org.eclipse.core.runtime.FileLocator;
    import org.eclipse.core.runtime.IAdaptable;
    import org.eclipse.core.runtime.Path;
    import org.eclipse.core.runtime.Platform;
    import org.eclipse.jface.resource.ImageDescriptor;
    import org.eclipse.ui.application.IWorkbenchConfigurer;
    import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
    import org.eclipse.ui.application.WorkbenchAdvisor;
    import org.eclipse.ui.application.WorkbenchWindowAdvisor;
    import org.eclipse.ui.ide.IDE;
    import org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages;
    import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
    import org.osgi.framework.Bundle;
    ...
    @Override
    public void initialize(IWorkbenchConfigurer configurer) {
    super.initialize(configurer);
    //configurer.setSaveAndRestore(true); // if you want
    IDE.registerAdapters();
    declareWorkbenchImages();
    }
    ...
    /**
    * Declares all IDE-specific workbench images. This includes both "shared"
    * images (named in {@link IDE.SharedImages}) and internal images (named in
    * {@link org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages}).
    *
    * @see IWorkbenchConfigurer#declareImage
    */
    private void declareWorkbenchImages() {

    final String ICONS_PATH = "$nl$/icons/full/";//$NON-NLS-1$
    final String PATH_ELOCALTOOL = ICONS_PATH + "elcl16/"; // Enabled //$NON-NLS-1$

    // toolbar
    // icons.
    final String PATH_DLOCALTOOL = ICONS_PATH + "dlcl16/"; // Disabled //$NON-NLS-1$
    // //$NON-NLS-1$
    // toolbar
    // icons.
    final String PATH_ETOOL = ICONS_PATH + "etool16/"; // Enabled toolbar //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons.
    final String PATH_DTOOL = ICONS_PATH + "dtool16/"; // Disabled toolbar //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons.
    final String PATH_OBJECT = ICONS_PATH + "obj16/"; // Model object //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons
    final String PATH_WIZBAN = ICONS_PATH + "wizban/"; // Wizard //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons

    // View icons
    final String PATH_EVIEW= ICONS_PATH + "eview16/"; //$NON-NLS-1$


    Bundle ideBundle = Platform.getBundle(IDEWorkbenchPlugin.IDE_WORKBENCH);

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC, PATH_ETOOL
    + "build_exec.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_HOVER,
    PATH_ETOOL + "build_exec.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_DISABLED,
    PATH_DTOOL + "build_exec.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC, PATH_ETOOL
    + "search_src.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC_HOVER,
    PATH_ETOOL + "search_src.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC_DISABLED,
    PATH_DTOOL + "search_src.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_NEXT_NAV, PATH_ETOOL
    + "next_nav.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PREVIOUS_NAV, PATH_ETOOL
    + "prev_nav.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_NEWPRJ_WIZ, PATH_WIZBAN
    + "newprj_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_NEWFOLDER_WIZ,
    PATH_WIZBAN + "newfolder_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_NEWFILE_WIZ, PATH_WIZBAN
    + "newfile_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_IMPORTDIR_WIZ,
    PATH_WIZBAN + "importdir_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_IMPORTZIP_WIZ,
    PATH_WIZBAN + "importzip_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_EXPORTDIR_WIZ,
    PATH_WIZBAN + "exportdir_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_EXPORTZIP_WIZ,
    PATH_WIZBAN + "exportzip_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_RESOURCEWORKINGSET_WIZ,
    PATH_WIZBAN + "workset_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_DLGBAN_SAVEAS_DLG, PATH_WIZBAN
    + "saveas_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_DLGBAN_QUICKFIX_DLG, PATH_WIZBAN
    + "quick_fix.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJ_PROJECT,
    PATH_OBJECT + "prj_obj.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDE.SharedImages.IMG_OBJ_PROJECT_CLOSED, PATH_OBJECT
    + "cprj_obj.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OPEN_MARKER,
    PATH_ELOCALTOOL + "gotoobj_tsk.gif", true); //$NON-NLS-1$


    // Quick fix icons
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ELCL_QUICK_FIX_ENABLED,
    PATH_ELOCALTOOL + "smartmode_co.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_DLCL_QUICK_FIX_DISABLED,
    PATH_DLOCALTOOL + "smartmode_co.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_WARNING,
    PATH_OBJECT + "quickfix_warning_obj.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_ERROR,
    PATH_OBJECT + "quickfix_error_obj.gif", true); //$NON-NLS-1$


    // task objects
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_HPRIO_TSK,
    // PATH_OBJECT+"hprio_tsk.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_MPRIO_TSK,
    // PATH_OBJECT+"mprio_tsk.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_LPRIO_TSK,
    // PATH_OBJECT+"lprio_tsk.gif");

    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJS_TASK_TSK,
    PATH_OBJECT + "taskmrk_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJS_BKMRK_TSK,
    PATH_OBJECT + "bkmrk_tsk.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_COMPLETE_TSK, PATH_OBJECT
    + "complete_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_INCOMPLETE_TSK, PATH_OBJECT
    + "incomplete_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_ITEM, PATH_OBJECT
    + "welcome_item.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_BANNER, PATH_OBJECT
    + "welcome_banner.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_ERROR_PATH, PATH_OBJECT
    + "error_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_WARNING_PATH, PATH_OBJECT
    + "warn_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_INFO_PATH, PATH_OBJECT
    + "info_tsk.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_LCL_FLAT_LAYOUT, PATH_ELOCALTOOL
    + "flatLayout.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_LCL_HIERARCHICAL_LAYOUT,
    PATH_ELOCALTOOL + "hierarchicalLayout.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEM_CATEGORY,
    PATH_ETOOL + "problem_category.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW,
    PATH_EVIEW + "problems_view.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_ERROR,
    PATH_EVIEW + "problems_view_error.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_WARNING,
    PATH_EVIEW + "problems_view_warning.gif", true); //$NON-NLS-1$

    // synchronization indicator objects
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_WBET_STAT,
    // PATH_OVERLAY+"wbet_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_SBET_STAT,
    // PATH_OVERLAY+"sbet_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_CONFLICT_STAT,
    // PATH_OVERLAY+"conflict_stat.gif");

    // content locality indicator objects
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_NOTLOCAL_STAT,
    // PATH_STAT+"notlocal_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_LOCAL_STAT,
    // PATH_STAT+"local_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_FILLLOCAL_STAT,
    // PATH_STAT+"filllocal_stat.gif");
    }

    /**
    * Declares an IDE-specific workbench image.
    *
    * @param symbolicName
    *            the symbolic name of the image
    * @param path
    *            the path of the image file; this path is relative to the base
    *            of the IDE plug-in
    * @param shared
    *            <code>true</code> if this is a shared image, and
    *            <code>false</code> if this is not a shared image
    * @see IWorkbenchConfigurer#declareImage
    */
    private void declareWorkbenchImage(Bundle ideBundle, String symbolicName,
    String path, boolean shared) {
    URL url = FileLocator.find(ideBundle, new Path(path), null);
    ImageDescriptor desc = ImageDescriptor.createFromURL(url);
    getWorkbenchConfigurer().declareImage(symbolicName, desc, shared);
    }

    Additional resources:

    Types of Applications You Can Create with Eclipse Platform

    The Eclipse Platform provides many tools and frameworks to build your application. They are flexible so you can do it in several ways according to your requirements.

    Here are the most common application types:

    1. OSGi (Equinox) Application

    Using the bare minimum OSGi runtime Equinox (or your favorite OSGi runtime).
    You can use OSGi services and OSGi bundles, but not loading the Eclipse Runtime (org.eclipse.core.runtime).

    2. Eclipse Console Application

    Using Eclipse Runtime (org.eclipse.core.runtime), but without any UI bundles.
    You can actually do a lot of things in this mode.

    3. Server/Daemon-style Eclipse Console Application

    This is actually just an Eclipse Console application, but that does not exit and continue running in the background.
    Daemon applications usually started with the -noExit command line argument or configuration.

    4. Eclipse Rich Client Platform (RCP) for Desktop Applications

    The most typical Eclipse application for the desktop.
    Uses the org.eclipse.rcp plugin.
    With features like perspectives, views, Common Navigator Framework, editors, action bars (toolbars), status line, menus, popup menus, ...
    It offers the rich plugins and functionality provided by Eclipse framework, and manageable overhead (disk space, memory usage, and performance).
    A well designed Eclipse RCP application starts just as fast as a hand-coded Swing application!
    Don't let the (slow) startup time of Eclipse Java IDE fool you...

    5. Eclipse IDE Application

    The most representative example is the Eclipse Java IDE itself.
    Uses org.eclipse.ui.ide plugin and other plugins, including org.eclipse.jdt.ui, org.eclipse.team.ui.
    Most application developers do not use these plugins, for smaller disk and memory footprint requirements.
    However, just to let you know that you can use them if you want. :-)

    6. Eclipse Rich Ajax Platform (RAP) for Web Applications

    The new kid on the block, but is already very potential and is gaining momentum very fast.
    Uses org.eclipse.rap.ui plugin.
    RAP applications can be single sourced so that it runs on both Eclipse RCP and Eclipse RAP runtimes, however you need to be aware that RAP applications are multi-user / multi-session, where RCP applications are single user.
    The most common Eclipse UI plugins are supported by RAP runtime (including EMF Editor UI), and more (Draw2d, Common Navigator Framework aka CNF) are still coming soon.

    7. Eclipse Riena RCP Application

    Framework for multi-tier / distributed application development.
    Write your plugins / application components in an uniform approach.
    Then deploy them in any side, whether in server or client-side.
    Eclipse Riena applications have at least two deployment artifacts, the one in client and the one in server.
    However, components do not care about the deployment, you write the components using the same framework.
    Riena abstracts the details of the deployment.

    8. Eclipse Riena RAP Application

    The exact same application that you can write on Eclipse Riena RCP, can be deployed as a web application using RAP frontend!

    9. Standalone Java application

    Contrary to what you might have believed, many Eclipse projects that bears the prefix "org.eclipse" in its name do not require running within Eclipse Runtime.
    For example, EMF (Eclipse Modeling Framework) runs fine within a standard Java static main() method.
    You just need to configure several stuff, that are normally done automatically using Eclipse extension mechanism. ;-)

    Reusing EMF.Edit-generated ItemProviders for implementing Eclipse UI IContentProvider



    Eclipse Platform allows us to be more productive by reusing many parts of the framework.

    For example, whether we want to display content in a standard TreeViewer SWT widget or the much more versatile Common Navigator Framework (CNF), we can reuse the same objects for both.

    We need to provide a model (the object itself) and two presenters: an ITreeContentProvider (which extends the generic IContentProvider) and an ILabelProvider. [Note: I just realized that this is the real world example of the often hailed MVP pattern aka Model-View-Presenter!]

    While you can implement a content provider and label provider yourself, if you're like me it's sooo much easier if you just reuse Eclipse's BaseWorkbenchContentProvider and WorkbenchLabelProvider and then make your objects adaptable to IWorkbenchAdapter.

    Overview


    There are three steps to make this happen:
    1. Implement IWorkbenchAdapter for your model class
    2. Create a org.eclipse.core.runtime.IAdapterFactory implementation that can create IWorkbenchAdapter adapters for your model classes
    3. Register your IAdapterFactory to the Eclipse Platform adapter manager
    As noted in the post title, I have a surprise shortcut for you! ;-)

    But just to make things interesting, I'll describe in detail how laborious it still is to adapt to IWorkbenchAdapter even by reusing BaseWorkbenchContentProvider and WorkbenchLabelProvider for your models.

    Implementing IWorkbenchAdapter


    Here is one way to implement IWorkbenchAdapter :

    import org.eclipse.jface.resource.ImageDescriptor;
    import org.eclipse.ui.model.IWorkbenchAdapter;
    import org.eclipse.ui.plugin.AbstractUIPlugin;
    import com.abispulsa.Contact;
    public class ContactWorkbenchAdapter implements IWorkbenchAdapter {
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getChildren(java.lang.Object)
    */
    @Override
    public Object[] getChildren(Object o) {
    return ((Contact)o).getMobileNumbers().toArray();
    }
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object)
    */
    @Override
    public ImageDescriptor getImageDescriptor(Object object) {
    return AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/user.png");
    }
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getLabel(java.lang.Object)
    */
    @Override
    public String getLabel(Object o) {
    return ((Contact)o).getName();
    }
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getParent(java.lang.Object)
    */
    @Override
    public Object getParent(Object o) {
    return ((Contact)o).eContainer();
    }
    }

    To be honest, I think the name IWorkbenchAdapter is confusing. It would be clearer if the interface name is IWorkbenchContent, simply for the fact that BaseWorkbenchContentProvider expects the objects to be an instance of IWorkbenchAdapter. The fact that we should adapt our objects to IWorkbenchAdapter instead of implementing IWorkbenchAdapter interface directly in our objects in another matter. In other words, it's possible to use BaseWorkbenchContentProvider with our objects *without* making our objects "adapt" to anything else, as long as it implements IWorkbenchAdapter.

    The above is pretty much a manual way to implement a IWorkbenchAdapter for model class. And of course, you have to do that for *every* model class you have. One adapter per model class. That's scary, huh?! Unless you have a predefined interface or base class... but I have a better idea (surprise! but later on below..)

    Creating the IAdapterFactory


    This is the less painful step, implement IAdapterFactory that can create IWorkbenchAdapter for your objects :

    import org.eclipse.core.runtime.IAdapterFactory;
    import org.eclipse.ui.model.IWorkbenchAdapter;
    import com.abispulsa.Contact;
    import com.abispulsa.MobileNumber;
    import com.abispulsa.Organization;
    public class AbisPulsaAdapterFactory implements IAdapterFactory {
    private ContactWorkbenchAdapter contactAdapter = new ContactWorkbenchAdapter();
    private OrganizationWorkbenchAdapter organizationAdapter = new OrganizationWorkbenchAdapter();
    private MobileNumberWorkbenchAdapter mobileNumberAdapter = new MobileNumberWorkbenchAdapter();

    /* (non-Javadoc)
    * @see org.eclipse.core.runtime.IAdapterFactory#getAdapter(java.lang.Object, java.lang.Class)
    */
    @Override
    public Object getAdapter(Object adaptableObject, Class adapterType) {
    if (adapterType == IWorkbenchAdapter.class) {
    if (adaptableObject instanceof Contact)
    return contactAdapter;
    else if (adaptableObject instanceof Organization)
    return organizationAdapter;
    else if (adaptableObject instanceof MobileNumber)
    return mobileNumberAdapter;
    }
    return null;
    }
    /* (non-Javadoc)
    * @see org.eclipse.core.runtime.IAdapterFactory#getAdapterList()
    */
    @Override
    public Class[] getAdapterList() {
    return new Class[] { IWorkbenchAdapter.class };
    }
    }

    Register the Factory for the Model Classes

    For each model class that can be adapted to IWorkbenchAdapter using your adapter factory, register it to the Eclipse Platform :

    import org.eclipse.core.runtime.Platform;
    import com.abispulsa.Contact;
    ...
    private AbisPulsaAdapterFactory adapterFactory = new AbisPulsaAdapterFactory();
    ...
    Platform.getAdapterManager().registerAdapters(adapterFactory, Contact.class);

    Using BaseWorkbenchContentProvider in the TreeViewer

    It's actually pretty standard stuff, but in case I have someone new to SWT/Eclipse/RCP/RAP ecosystem, this should provided a quick orientation to connect the above stuff:

    import org.eclipse.jface.viewers.TreeViewer;
    import org.eclipse.swt.SWT;
    import org.eclipse.ui.model.BaseWorkbenchContentProvider;
    import org.eclipse.ui.model.WorkbenchLabelProvider;
    import com.abispulsa.AbispulsaFactory;
    ...
    treeViewer = new TreeViewer(parent, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
    getSite().setSelectionProvider(treeViewer);
    treeViewer.setLabelProvider(new WorkbenchLabelProvider());
    treeViewer.setContentProvider(new BaseWorkbenchContentProvider());
    ...
    Contact contact = AbispulsaFactory.eINSTANCE.createContact();
    contact.setName("Hendy Irawan");
    treeViewer.setInput(contact);
    Done!!!

    Reflecting Back


    WHOA....! A lot of stuff just to get something displayed on a tree viewer!
    Actually, it's not that bad and it's quite powerful. Eclipse Platform provides :
    • Your objects can now be displayed in pretty much any kind of exotic view possible. BaseWorkbenchContentProvider and WorkbenchLabelProvider will handle all that for you, consistently.
    • Your objects can, of course, be displayed inside a Common Navigator Framework (CNF) view, which can display several objects using a flexible combination of content providers, actions, popup menus, etc.
    With other (non-Eclipse) frameworks, with the same amount of code you'd still be a looooong way to achieving the flexibility that Eclipse Platform provides.
    Eclipse FTW!

    But wait... EMF Comes to the Rescue


    I haven't even written the main content of my post yet ! Hehe...

    Some of you may notice that I used EMF to generate my model classes above. (Hurray!)

    While happily hacking my IWorkbenchAdapter adapters (see the confusion? it's not cool to write "IWorkbenchAdapter adapter"), I noticed that I have to set the image for the model, however I already had the images provided by the generated EMF-Edit providers. This practically makes the image on my workbench model different from the one used by EMF Editors.

    Here's the generated *EMF* Item Provider :

    /**
    * This is the item provider adapter for a {@link com.abispulsa.Organization} object.
    * <!-- begin-user-doc -->
    * <!-- end-user-doc -->
    * @generated
    */
    public class OrganizationItemProvider
    extends ItemProviderAdapter
    implements
    IEditingDomainItemProvider,
    IStructuredItemContentProvider,
    ITreeItemContentProvider,
    IItemLabelProvider,
    IItemPropertySource {
    ...
    /**
    * This returns Organization.png.
    * <!-- begin-user-doc -->
    * <!-- end-user-doc -->
    * @generated NOT
    */
    @Override
    public Object getImage(Object object) {
    return overlayImage(object, getResourceLocator().getImage("full/obj16/Organization.png"));
    }

    Hey.... I can reuse that!

    So I change this hardcoded piece :

    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object)
    */
    @Override
    public ImageDescriptor getImageDescriptor(Object object) {
    return AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/organization.png");
    }

    Into something more consistent, delegate the stuff to my EMF Edit provider:

    private OrganizationItemProvider itemProvider = (OrganizationItemProvider) new AbispulsaItemProviderAdapterFactory().createOrganizationAdapter();
    ...
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object)
    */
    @Override
    public ImageDescriptor getImageDescriptor(Object object) {
    return ImageDescriptor.createFromURL((URL) itemProvider.getImage(object));
    }

    When I did that, I noticed that EMF.Edit's ItemProvider provides other methods, like getChildren(), getText(), etc. that I can use... So I hack away and change the IWorkbenchAdapter implementation :

    public class OrganizationWorkbenchAdapter implements IWorkbenchAdapter {
    private OrganizationItemProvider itemProvider = (OrganizationItemProvider) new AbispulsaItemProviderAdapterFactory().createOrganizationAdapter();
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getChildren(java.lang.Object)
    */
    @Override
    public Object[] getChildren(Object o) {
    return itemProvider.getChildren(o).toArray();
    //return ((Organization)o).getContacts().toArray();
    }
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object)
    */
    @Override
    public ImageDescriptor getImageDescriptor(Object object) {
    return ImageDescriptor.createFromURL((URL) itemProvider.getImage(object));
    }
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getLabel(java.lang.Object)
    */
    @Override
    public String getLabel(Object o) {
    return itemProvider.getText(o);
    //return ((Organization)o).getName();
    }
    /* (non-Javadoc)
    * @see org.eclipse.ui.model.IWorkbenchAdapter#getParent(java.lang.Object)
    */
    @Override
    public Object getParent(Object o) {
    return itemProvider.getParent(o);
    //return ResourcesPlugin.getWorkspace().getRoot();
    }
    }

    I put the original method implementations there so you can compare. The original implementations are so tightly coupled to the model structure, the EMF.Edit-powered ones are more generic.

    Then I noticed that I practically has no dependency to the original model (Organization class), because I can change :

    private OrganizationItemProvider itemProvider = (OrganizationItemProvider) new AbispulsaItemProviderAdapterFactory().createOrganizationAdapter();
    into something like:
    AbispulsaItemProviderAdapterFactory factory = new AbispulsaItemProviderAdapterFactory();
    ITreeItemContentProvider treeItemProvider = (ITreeItemContentProvider) factory.adapt(object, ITreeItemContentProvider.class);
    IItemLabelProvider labelProvider = (IItemLabelProvider) factory.adapt(object, IItemLabelProvider.class);

    Be surprised that right now I don't have any mention of the original model's class name anywhere!

    Content Provider Wars Episode V: The EMF Strikes Back!

    Unfortunately the ITreeItemContentProvider etc. interfaces are EMF's, and it's unusable by SWT and other Eclipse UI views, which requires IWorkbenchAdapter or IContentProvider and ILabelProvider.

    Then something struck me: come on, since so much infrastructure has been provided by EMF, surely EMF wouldn't leave out something so obvious: a content provider and a label provider!

    Enter the warlords:

    These providers are contained within the plugin org.eclipse.emf.edit.ui.
    We only need to subclass these providers and provide the our own list of org.eclipse.emf.common.notify.AdapterFactory objects :

    Our EMF-powered Content Provider :

    import org.eclipse.emf.common.notify.AdapterFactory;
    import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
    import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
    import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
    import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
    import com.abispulsa.provider.AbispulsaItemProviderAdapterFactory;
    public class AbispulsaContentProvider extends AdapterFactoryContentProvider {
    private static AdapterFactory adapterFactory;

    static {
    adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
    new ResourceItemProviderAdapterFactory(),
    new AbispulsaItemProviderAdapterFactory(),
    new ReflectiveItemProviderAdapterFactory()
    });
    }

    public AbispulsaContentProvider() {
    super(adapterFactory);
    }

    }

    Our EMF-powered Label Provider : (identical code to the above!)

    import org.eclipse.emf.common.notify.AdapterFactory;
    import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
    import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
    import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
    import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
    import com.abispulsa.provider.AbispulsaItemProviderAdapterFactory;
    public class AbispulsaLabelProvider extends AdapterFactoryLabelProvider {
    private static AdapterFactory adapterFactory;

    static {
    adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
    new ResourceItemProviderAdapterFactory(),
    new AbispulsaItemProviderAdapterFactory(),
    new ReflectiveItemProviderAdapterFactory()
    });
    }

    /**
    * @param adapterFactory
    */
    public AbispulsaLabelProvider() {
    super(adapterFactory);
    }
    }

    Using Our EMF-powered Content and Label Providers

    For an SWT TreeViewer :

    treeViewer.setLabelProvider(new AbispulsaLabelProvider());
    treeViewer.setContentProvider(new AbispulsaContentProvider());

    Right now it's safe to throw all those painfully coded IWorkbenchAdapter, UI AdapterFactory's, and the adapter factory registration code to the dumpster. ;-)

    Super quick, huh? :-)

    A nice side effect is you don't rely on any "workbench" stuff... But of course now you rely on EMF Edit UI :-)

    Another nice side effect is the content/label providers actually can be used with any EMF EObject, due to ReflectiveItemProviderAdapterFactory magic. :-)

    EMF-powered Content in Common Navigator Framework (CNF) View

    Enough with the Star Wars talk, it's time to show how beautiful that concise piece of code working inside the powerful Common Navigator Framework (CNF) View.

    Declaratively of course!

    Here's the relevant parts in plugin.xml. The XML codes below might seem scary to readers who aren't familiar yet with how Eclipse plugin extensions work, but in reality there's no XML typing involved at all! All these extensions are editable using PDE's Extensions visual editor.

    Declare the CNF view part:

       <extension point="org.eclipse.ui.views">
          <category id="com.abispulsa.ui.abispulsa_category" name="AbisPulsa">
          </category>
          <view allowMultiple="false" category="com.abispulsa.ui.abispulsa_category"
                class="org.eclipse.ui.navigator.CommonNavigator" icon="icons/contacts.ico"
                id="com.abispulsa.ui.contactsNav_view" name="Contacts Nav"
                restorable="true">
          </view>
       </extension>

    Put the view in perspective: (in this case, all perspectives)

       <extension point="org.eclipse.ui.perspectiveExtensions">
          <perspectiveExtension targetID="*">
             <view id="com.abispulsa.ui.contactsNav_view" minimized="false"
                   ratio="0.4" relationship="left" relative="org.eclipse.ui.editorss">
             </view>
          </perspectiveExtension>
       </extension>

    Declare our NCE (Navigator Content Extension), which uses our EMF-powered content and label providers, handling all EObject instances:

       <extension point="org.eclipse.ui.navigator.navigatorContent">
          <navigatorContent id="com.abispulsa.ui.modelContent" name="Model"
                contentProvider="com.abispulsa.presentation.AbispulsaContentProvider"
                labelProvider="com.abispulsa.presentation.AbispulsaLabelProvider">
             <triggerPoints>
                <instanceof value="org.eclipse.emf.ecore.EObject">
                </instanceof>
             </triggerPoints>
          </navigatorContent>
       </extension>

    Then bind our CNF navigator view with our NCE :

       <extension point="org.eclipse.ui.navigator.viewer">
          <viewerContentBinding viewerId="com.abispulsa.ui.contactsNav_view">
             <includes>
                <contentExtension pattern="com.abispulsa.ui.modelContent" />
             </includes>
          </viewerContentBinding>
       </extension>

    Done!

    The real beauty of the EMF enhanced solution is you actually have to set it up only once. From now on all changes you do to your models will be reflected directly in the UI content/label providers and all the views that use your EMF models. How cool is that? ;-)

    PS. For your viewing pleasure, here's the resulting Common Navigator Framework (CNF) view, powered by EMF models. :-)

    P.P.S. As a bonus, for those who notice, these EMF models are powered by CDO Model Repository ! ;-)

    Additional resources:

    Tuesday, December 28, 2010

    Logging with SLF4J and LogBack in Eclipse Application (with Hibernate)

    During my EMF persistence adventures with CDO 3.0, Teneo 1.1.2, and Hibernate 3.3.2 there's something bugging me: The Hibernate bundle from Elver.org includes several out-of-context dependencies SLF4J API, SLF4J NOP implementation, SLF4J Log4j implementation and Log4j.

    Bundling logging JARs is already strange enough, but the provided Log4j is of version 1.2.8 while SLF4j Log4j 1.5.8 requires at least Log4j 1.2.12. Even more is it bundles SLF4j NOP implementation, why???

    Since it's embedded, it makes it impossible for me to use the embedded SLF4j and Log4j for my own purposes.

    So I decided to fix things a bit. This is quite unproductive, but I guess it's an okay exercise.

    First I have to delete several files from the exploded org.hibernate_3.3.2.201005082216 plugin:

    1. slf4j-api-*.jar
    2. slf4j-log4j-*.jar
    3. slf4j-nop-*.jar
    4. log4j-*.jar
    Deleting these files "saves" about 400 KB of space. :-) Not bad.

    Then I have to change the META-INF/MANIFEST.MF of the Hibernate plugin:

    Edit the Bundle-ClassPath from:

    Bundle-ClassPath: antlr-2.7.6.jar,c3p0-0.9.1.jar,commons-collections-3
    .1.jar,ehcache-1.2.3.jar,hibernate3.jar,hibernate-annotations.jar,hib
    ernate-commons-annotations.jar,hibernate-entitymanager.jar,javassist-
    3.11.0.GA.jar,jta-1.1.jar,log4j-1.2.8.jar,slf4j-api-1.5.8.jar,slf4j-l
    og4j12-1.5.8.jar,slf4j-nop-1.5.8.jar

    To:

    Bundle-ClassPath: antlr-2.7.6.jar,c3p0-0.9.1.jar,commons-collections-3
    .1.jar,ehcache-1.2.3.jar,hibernate3.jar,hibernate-annotations.jar,hib
    ernate-commons-annotations.jar,hibernate-entitymanager.jar,javassist-
    3.11.0.GA.jar,jta-1.1.jar

    With the unnecessary cruft removed, it's time to add SLF4j and Logback plugins to the target platform. Get these from Orbit:

    1. ch.qos.logback.classic_0.9.24.v20100831-0715.jar
    2. ch.qos.logback.core_0.9.24.v20100831-0715.jar
    3. ch.qos.logback.slf4j_0.9.24.v20100831-0715.jar
    4. org.slf4j.api_1.6.1.v20100831-0715.jar
    5. org.slf4j.jcl_1.6.1.v20100831-0715.jar

    In total they are 619 KB. Not bad for a shared logging system. :-)

    Now remove/exclude org.apache.commons.logging plugin from your target platform because org.slf4j.jcl plugin replaces it.

    Done! That's it. SLF4j and Logback providing nice logging implementation inside the Eclipse platform application.

    A nice side effect is that log4j.properties is no longer needed, because Logback works fine without configuration.
    But you can also configure Logback.

    Also checkout Logging in OSGi Applications by ekkes-corner for more thorough explanation.

    CDO 3.0 with Hibernate Experiences

    CDO allows efficient access to EMF repositories over a network, with pluggable persistence backends including Hibernate, which under the hood uses Teneo for its mapping.

    CDO 3.0 is released. The newer CDO 4.0 is still in development.

    CDO 4.0 should work with Teneo 1.1.2 and Hibernate 3.6.

    With CDO 3.0, I'm quite confused which one I should use:
    • Teneo 1.1.2 + Hibernate 3.3.2
    • Teneo 1.2.0 + Hibernate 3.3.2
    • Teneo 1.2.0 + Hibernate 3.6
    It seemed the third option is easily out, because for example, CDO 3.0's CDOPropertyGetter, does not support the new members of Hibernate 3.6 Getter interface, which is getMember() :
    import org.hibernate.HibernateException;
    import org.hibernate.engine.SessionImplementor;
    import org.hibernate.property.Getter;
    import java.lang.reflect.Method;
    import java.util.Map;
    /**
    * TODO How does this differ from {@link CDORevisionSetter}? Both needed?
    *
    * @author Martin Taal
    */
    public class CDOPropertyGetter extends CDOPropertyHandler implements Getter

    (from  org.eclipse.emf.cdo.server.hibernate/src/org/eclipse/emf/cdo/server/internal/hibernate/tuplizer/CDOPropertyGetter.java)

    CDO Wiki said CDO 3.0 should work with Teneo 1.2.0 where Teneo wiki said CDO 3.0 only works with Teneo 1.1.2.

    I'm now running CDO 3.0 example with Teneo 1.2.0 but with Hibernate 3.3.2 instead of Hibernate 3.6. And it seems to work fine (I haven't tested thoroughly). I guess it's not about the Teneo version but the Hibernate version? (Meaning Teneo 1.2.0 is able to work with either Hibernate 3.3.2 or Hibernate 3.6? I'm not sure yet though.)

    Another strange thing is that Teneo 1.2.0 plugins from Teneo 1.2.0 update site is versioned as 1.1.2.v201011240719, not 1.2.0 ...? (the EMF Teneo features are versioned as 1.2.0... which signifies the confusion). Am I doing something wrong or getting from the wrong update site?

    Unfortunately the Eclipse newsgroup seems to be down on me right now so I can't post there so I posted it to my blog, hoping that this will provide some information on others wanting to taste CDO. :-)

    Another tip: Previously I got an error:

    IllegalStateException: Not able to update container columns of CDOResource with id OID:http://www.eclipse.org/emf/CDO/Eresource/2.0.0#CDOResource#1

    Turned out it was due to me putting several duplicate Teneo plugins of different versions. (common sense, I know.)

    I hope Eike Stepper or Martin Taal steps in (pun not intended) to clarify more on this.

    I'll update this post when more information arrives. :)

    Update: It seems my suspicion is incorrect, when trying to run the example CDO Client, I got the following error:

    !ENTRY org.eclipse.osgi 2 0 2010-12-29 01:25:07.835
    !MESSAGE One or more bundles are not resolved because the following root constraints are not resolved:
    !SUBENTRY 1 org.eclipse.osgi 2 0 2010-12-29 01:25:07.835
    !MESSAGE Bundle reference:file:/home/ceefour/project/AbisPulsa/p2/Teneo/org.eclipse.emf.teneo.hibernate_1.1.2.v201011240719.jar was not resolved.
    !SUBENTRY 2 org.eclipse.emf.teneo.hibernate 2 0 2010-12-29 01:25:07.835
    !MESSAGE Missing imported package org.hibernate.engine.query_[3.6.0,4.0.0).
    !SUBENTRY 1 org.eclipse.osgi 2 0 2010-12-29 01:25:07.836
    !MESSAGE Bundle reference:file:/home/ceefour/project/AbisPulsa/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.emf.query.ocl_2.0.0.v20091215-1624.jar was not resolved.
    !SUBENTRY 2 org.eclipse.emf.query.ocl 2 0 2010-12-29 01:25:07.836
    !MESSAGE Missing required bundle org.eclipse.ocl_[3.0.0,4.0.0).

    So it seems Teneo 1.2.0 (which actually uses the JAR version 1.1.2.v201011240719) requires at least Hibernate 3.6.
    Since CDO 3.0 Hibernate Store only implements Hibernate 3.3.2 API, which means that it's impossible to use CDO 3.0 with Teneo 1.2.0, only with Teneo 1.1.2. (so Teneo wiki is correct, CDO wiki is wrong..?)

    Monday, December 27, 2010

    Querying EMF Models using Model Query

    Querying i.e. retrieving the list of EMF objects that meet a specific criteria by only navigating the object graph, finding the right objects, plus loops and if-elses... is inconvenient! Not to mention the performance of the lookup would be very slow on large models.

    Fortunately there are several solutions (actually, alternatives, since there are still some restrictions) for this:

    1. EMF Model Query
    2. EMF Model Query with OCL constraint language
    3. HQL (Hibernate Query Language) with CDO / Teneo and Hibernate Data Store

    Using EMF Model Query

    For now I'll only show you how it's done with EMF Model Query because it's the only one I've tried at this point. ;-)

    I'm currently porting one of my PHP apps to Eclipse Platform and EMF.

    Here's how it looks:

    /**
    * Finds a refill that is in Refill::STATUS_POSTED status matching this code.
    * @param string $voucher_code
    * @param string $mobile
    * @model
    */
    public Refill lookupPosted(String voucherCode, String destNumber) {
    EObjectAttributeValueCondition statusCondition = new EObjectAttributeValueCondition(
    AbispulsaPackage.Literals.REFILL__STATUS,
    new ObjectInstanceCondition(RefillStatus.POSTED));
    EObjectReferenceValueCondition voucherCondition = new EObjectReferenceValueCondition(
    AbispulsaPackage.Literals.REFILL__VOUCHER,
    new EObjectAttributeValueCondition(
    AbispulsaPackage.Literals.VOUCHER__CODE, new StringValue(
    voucherCode)));
    EObjectAttributeValueCondition destNumberCond = new EObjectAttributeValueCondition(
    AbispulsaPackage.Literals.REFILL__DEST_NUMBER, new StringValue(
    destNumber));
    SELECT statement = new SELECT(1, new FROM(this), new WHERE(
    statusCondition.AND(voucherCondition).AND(destNumberCond)));
    IQueryResult result = statement.execute();
    if (!result.isEmpty())
    return (Refill)result.iterator().next();
    else
    return null;
    }

    If you think the above code is verbose... Yeah, it certainly is! :-(

    Here's the original code in PHP :

    Refill refill = Doctrine_Query::create()->from('Refill')
    ->where('status = ?', self::STATUS_POSTED)
    ->andWhere('voucher_code = ?', $voucher_code)
    ->andWhere('dest_number = ?', $mobile)
    ->limit(1)->fetchOne();

    Much more compact. Not to mention it's definitely much faster than EMF Query since the PHP code directly accesses the backend storage (MySQL) with all the indexing goodies of Relational DBMS.

    However, it's still a good way than coding your own loops and ifs. For small models and if you don't mind verbosity, plain EMF Model Query perfect and very flexible.

    EMF Model Query with OCL

    EMF Model Query with OCL works just like the plain EMF Model Query, but with a much nicer and widely used standard constraint syntax.

    A simple OCL is :

    self.name = 'Bob'

    Expressing the same criteria in plain EMF Model Query would be much longer.

    Typical code looks like this (from EMF Model Query guide):

    Resource myResource = ... // get the resource

    OCL ocl = org.eclipse.ocl.ecore.OCL.newInstance(); Condition condition = new BooleanOCLCondition<EClassifier, EClass, EObject>( ocl.getEnvironment(), "self.books->collect(b : Book | b.category)->asSet()->size() > 2", EXTLibraryPackage.Literals.WRITER); SELECT statement = new SELECT(SELECT.UNBOUNDED, false, new FROM(myResource.getContents()), new WHERE(condition), new NullProgressMonitor()); IQueryResult results = statement.execute(); // do something with the results selectInEditor(results);
    See more EMF Model Query with OCL Examples.

    HQL on CDO/Teneo + Hibernate

    This one is sort of cheating, because it doesn't really use EMF capabilities at all, but under the hood delegates to the underlying persistence storage which is Hibernate which in turn is backed by a relational database (such as MySQL, HSQLDB, PostgreSQL, you name it).

    The objects returned by Hibernate are then "converted" into EMF Objects. Actually they're already EMF Objects (of course!) but I suspect CDO/Teneo does additional processing like attaching the objects to the proper EMF Resource.

    The code is much more compact and developer-friendly: (especially if you're a Hibernate developer)

    CDOSession session = openSession();
    CDOTransaction transaction = session.openTransaction();  
    CDOQuery cdoQuery = transaction.createQuery("hql", "from Product where vat=:vat"); cdoQuery.setParameter("vat", VAT.VAT15); List<Product> products = cdoQuery.getResult(Product.class);
    transaction.commit();
    Cool, huh? Compact, easy, and strongly typed! And it supports parameterized queries! And results are fast!

    More about HQL Support on CDO Eclipsepedia Wiki.

    Conclusion

    I think using HSQL is the best solution from both the performance standpoint and ease of use.

    However it requires to use a data store that supports a query language, such as the Hibernate Data Store (I think the CDO Objectivity data store should also support querying).

    In theory, CDO supports pluggable query handler so that CDO Data Stores are free to implement their own optimized query language (with parameter support!) However whether this applies in practice (for other data stores), I still have to find out. :-)

    Sunday, December 26, 2010

    How to Dump/Inspect Object or Variable in Java

    Scala (console) has a very useful feature to inspect or dump variables / object values :

    scala> def b = Map("name" -> "Yudha", "age" -> 27)
    b: scala.collection.immutable.Map[java.lang.String,Any]

    scala> b
    res1: scala.collection.immutable.Map[java.lang.String,Any] = Map((name,Yudha), (age,27))

    Inside our application, especially in Java programming language (although the techniques below obviously works with any JVM language like Scala and Groovy) sometimes we want to inspect/dump the content of an object/value. Probably for debugging or logging purposes.

    My two favorite techniques is just to serialize the Java object to JSON and/or XML. An added benefit is that it's possible to deserialize the dumped object representation back to an actual object if you want.

    JSON Serialization with Jackson

    Depend on Jackson (using Maven):
    <dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.6.3</version>
    </dependency>
    Then use it:
    import org.codehaus.jackson.JsonGenerationException;
    import org.codehaus.jackson.map.JsonMappingException;
    import org.codehaus.jackson.map.ObjectMapper;
    import org.codehaus.jackson.map.SerializationConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    ..
    Logger logger = LoggerFactory.getLogger(getClass());

    @Test
    public void level() throws ServiceException, JsonGenerationException, JsonMappingException, IOException {
    MagentoServiceLocator locator = new MagentoServiceLocator();
    Mage_Api_Model_Server_HandlerPortType port = locator.getMage_Api_Model_Server_HandlerPort();
    String sessionId = port.login("...", "...");
    logger.info(String.format("Session ID = %s", sessionId));
    Map[] categories = (Map[]) port.call(sessionId, "catalog_category.level", new Object[] { null, null, 2 } );
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
    logger.info( mapper.writeValueAsString(categories) );
    }

    Example output :

    6883 [main] INFO id.co.bippo.shop.magentoclient.AppTest - [ {
      "position" : "1",
      "level" : "2",
      "is_active" : "1",
      "name" : "Gamis",
      "category_id" : "3",
      "parent_id" : 2
    }, {
      "position" : "2",
      "level" : "2",
      "is_active" : "1",
      "name" : "Celana",
      "category_id" : "5",
      "parent_id" : 2
    } ]

    XML Serialization with XStream

    As a pre-note, XStream can also handle JSON with either Jettison or its own JSON driver, however people usually prefer Jackson than XStream for JSON serialization.

    Maven dependency for XStream:
    <dependency>
    <groupId>xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.2.2</version>
    </dependency>
    Use it:
    import java.io.IOException;
    import java.rmi.RemoteException;
    import java.util.Map;

    import javax.xml.rpc.ServiceException;

    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import com.thoughtworks.xstream.XStream;
    ...
    @Test
    public void infoXml() throws ServiceException, RemoteException {
    MagentoServiceLocator locator = new MagentoServiceLocator();
    Mage_Api_Model_Server_HandlerPortType port = locator.getMage_Api_Model_Server_HandlerPort();
    String sessionId = port.login("...", "...");
    logger.info(String.format("Session ID = %s", sessionId));
    Map category = (Map) port.call(sessionId, "catalog_category.info",
    new Object[] { 3 } );
    XStream xstream = new XStream();
    logger.info( xstream.toXML(category) );
    }

    Sample output:

    5949 [main] INFO id.co.bippo.shop.magentoclient.AppTest - <map>
      <entry>
        <string>position</string>
        <string>1</string>
      </entry>
      <entry>
        <string>custom_design</string>
        <string></string>
      </entry>
      <entry>
        <string>custom_use_parent_settings</string>
        <string>0</string>
      </entry>
      <entry>
        <string>custom_layout_update</string>
        <string></string>
      </entry>
      <entry>
        <string>include_in_menu</string>
        <string>1</string>
      </entry>
      <entry>
        <string>custom_apply_to_products</string>
        <string>0</string>
      </entry>
      <entry>
        <string>meta_keywords</string>
        <string>gamis, busana muslim</string>
      </entry>
      <entry>
        <string>available_sort_by</string>
        <string></string>
      </entry>
      <entry>
        <string>url_path</string>
        <string>gamis.html</string>
      </entry>
      <entry>
        <string>children</string>
        <string></string>
      </entry>
      <entry>
        <string>landing_page</string>
        <null/>
      </entry>
      <entry>
        <string>display_mode</string>
        <string>PRODUCTS</string>
      </entry>
      <entry>
        <string>level</string>
        <string>2</string>
      </entry>
      <entry>
        <string>description</string>
        <string>Gamis untuk muslimah</string>
      </entry>
      <entry>
        <string>name</string>
        <string>Gamis</string>
      </entry>
      <entry>
        <string>path</string>
        <string>1/2/3</string>
      </entry>
      <entry>
        <string>created_at</string>
        <string>2010-12-24 11:37:41</string>
      </entry>
      <entry>
        <string>children_count</string>
        <string>0</string>
      </entry>
      <entry>
        <string>is_anchor</string>
        <string>1</string>
      </entry>
      <entry>
        <string>url_key</string>
        <string>gamis</string>
      </entry>
      <entry>
        <string>parent_id</string>
        <int>2</int>
      </entry>
      <entry>
        <string>filter_price_range</string>
        <null/>
      </entry>
      <entry>
        <string>all_children</string>
        <string>3</string>
      </entry>
      <entry>
        <string>is_active</string>
        <string>1</string>
      </entry>
      <entry>
        <string>page_layout</string>
        <string></string>
      </entry>
      <entry>
        <string>image</string>
        <null/>
      </entry>
      <entry>
        <string>category_id</string>
        <string>3</string>
      </entry>
      <entry>
        <string>default_sort_by</string>
        <null/>
      </entry>
      <entry>
        <string>custom_design_from</string>
        <null/>
      </entry>
      <entry>
        <string>updated_at</string>
        <string>2010-12-24 11:37:41</string>
      </entry>
      <entry>
        <string>meta_description</string>
        <string>Jual baju gamis untuk muslim</string>
      </entry>
      <entry>
        <string>custom_design_to</string>
        <null/>
      </entry>
      <entry>
        <string>path_in_store</string>
        <null/>
      </entry>
      <entry>
        <string>meta_title</string>
        <string>Gamis</string>
      </entry>
      <entry>
        <string>increment_id</string>
        <null/>
      </entry>
    </map>

    Which one is better?

    I personally prefer JSON, but fortunately, you always have a choice. :-)

    Maven 3.0 does not support *Maven* 1.0 (legacy) repository

    Today I was frustrated by this :

    Maven 3.x no longer supports repositories using <layout>legacy</layout>. Users that need to access repositories created with Maven 1.x are advised to use a repository manager that is capable of providing a Maven 2.x compatible view of the legacy repository.

    WHAT???

    Sonatype could simply change their suggestion to "Users that need to access repositories created with Maven 1.x are advised to use Apache Ivy."

    I understand the need to force users to use the new technology, but not by disabling access to older technology without providing a practical alternative. At least wait for your competitors to stop supporting your legacy product before you yourself do it.

    java.net Maven 1 repository contains artifacts that I need at times, now I have to hunt the artifacts or do workarounds that was simply solved using repository layout=legacy. Argh!

    Friday, December 24, 2010

    Saving the xsi:schemaLocation into EMF XMI file

    Previously I've shared how to fix loading an EMF XMI file within Eclipse IDE that has no xsi:schemaLocation.

    The reason why it happened to me was everytime XMIResourceImpl.save() is called, it removes the xsi:schemaLocation from the saved XMI file. Which made the file not openable from Eclipse IDE again. I want to get rid of this problem once and for all!

    Thanks to Ed Merks, there are three ways to solve this problem:

    1. Install the plugin containing your EMF-generated package to the Eclipse IDE. This seems to be the proper way to do it. Better yet, you can install the generated EMF.Edit and EMF.Editor plugins so you can experience the real editor with all the features (including icons) you've prepared.
    2. Use XMIResource.OPTION_SCHEMA_LOCATION to serialize the xsi:schemaLocation. I'll detail this below.
    3. Change the package's nsURI so that it points to the real, absolute path of the Ecore file (or the metamodel, I think you can use an XML Schema/XSD or UML file as well). This can be a file:// URI or a http:// URI (I guess platform:/ URIs would work as well).

    Since the first and third option is self-explanatory, here's how to do the second option.

    Using XMIResource.OPTION_SCHEMA_LOCATION


    Open your *PackageImpl class and override the createResource() method to specify the actual schema (Ecore metamodel) location:

    @Override
    protected Resource createResource(String uri) {
    return super.createResource("file:/home/ceefour/project/AbisPulsa/workspace/com.abispulsa.model/src/model/abispulsa.ecore");
    }

    The method is not there so you have to create it (press Ctrl+Space on an empty space to bring up the code assist).

    Now when doing resource.save(), you need to set the XMIResource.OPTION_SCHEMA_LOCATION to true :

    log.info(String.format("Saving resource: %s", getCatalog().eResource().getURI()));
    HashMap<String, Object> opts = new HashMap<String, Object>();
    opts.put(XMIResource.OPTION_SCHEMA_LOCATION, true);
    getCatalog().eResource().save(opts);

    Now it saves the xsi:schemaLocation to the XMI file. :-)

    Also sourced from EMF Wiki: http://wiki.eclipse.org/index.php/EMF/FAQ#How_can_I_ensure_that_an_xsi:schemaLocation_is_serialized_for_my_packages.3F

    Thursday, December 23, 2010

    Creating a Daemon-style Eclipse "headless" Application

    A daemon application is an application that does not exit immediately and does not wait for any input/interaction from the user. Instead, it is (usually) providing a network service or by being a server. Apache Tomcat servlet container is one such daemon.

    To run  a daemon-style application in Eclipse runtime, it is tempting to just use the standard infinite loop:

    import org.eclipse.equinox.app.IApplication;
    ...

    public class Application implements IApplication {

    @Override
    public Object start(IApplicationContext context) throws Exception {
    // ... start services here (in another thread) ...
    while (true) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    break;
    }
    }
    return IApplication.EXIT_OK;
    }

    While this technique works, I believe the better way is just to immediately return normally. And use Eclipse's -noExit launch argument to let Eclipse know that we're still doing things in the background.

    So the start() method can be just like this:

    @Override
    public Object start(IApplicationContext context) throws Exception {
    final ResourceSetImpl resourceSet = new ResourceSetImpl();
    Resource dealerResource = resourceSet
    .createResource(URI
    .createFileURI("/home/ceefour/project/AbisPulsa/workspace/com.abispulsa.model/src/model/YahooDealer.xmi"));
    dealerResource.load(null);
    dealer = (YahooDealerImpl) dealerResource.getContents().get(0);

    dealer.signon();

    Resource catalogRes = resourceSet.createResource(URI.createFileURI("/home/ceefour/project/AbisPulsa/workspace/com.abispulsa.model/src/model/Catalog.xmi"));
    catalogRes.load(null);
    Catalog catalog = (Catalog) catalogRes.getContents().get(0);
    dealer.setCatalog(catalog);

    return IApplication.EXIT_OK;
    }

    Then edit the application's Launch Configuration and add the following to program arguments:

    -noExit -consoleLog

    You may also want to add the OSGi console (argument -console):

    -noExit -consoleLog -console

    Let's run 'em servers. :-)

    Solving inside Eclipse IDE: org.eclipse.emf.ecore.xmi.PackageNotFoundException: Package with uri 'http://*' not found

    When opening an XMI file (EMF model) inside the Eclipse Modeling IDE you may sometimes encounter the following error:

    org.eclipse.emf.ecore.resource.impl.ResourceSetImpl$1DiagnosticWrappedException: org.eclipse.emf.ecore.xmi.PackageNotFoundException: Package with uri 'http://www.abispulsa.com/model/1.0' not found. (platform:/resource/com.abispulsa.model/src/model/Catalog.xmi, 2, 126)
    at org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.handleDemandLoadException(ResourceSetImpl.java:315)
    at org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.demandLoadHelper(ResourceSetImpl.java:274)
    at org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.getResource(ResourceSetImpl.java:397)
    at org.eclipse.emf.ecore.presentation.EcoreEditor.createModelGen(EcoreEditor.java:1062)
    at org.eclipse.emf.ecore.presentation.EcoreEditor.createModel(EcoreEditor.java:1082)
    at org.eclipse.emf.ecore.presentation.EcoreEditor.createPages(EcoreEditor.java:1147)
    ...

    When this happens in your application, you can register the package to fix it.

    However in this case the error happens when you want to edit an XMI file (EMF model) inside the Eclipse IDE.

    To fix it open the file using the plain XML Editor, and change the line :

    <abispulsa:Catalog xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:abispulsa="http://www.abispulsa.com/model/1.0">

    to point to the metamodel's location using xsi:schemaLocation :

    <abispulsa:Catalog xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:abispulsa="http://www.abispulsa.com/model/1.0" xsi:schemaLocation="http://www.abispulsa.com/model/1.0 abispulsa.ecore">

    Now you can open the file in the EMF Editor successfully.

    Fixing EMF/Ecore/XMI Resource loading error: Package with URI ... not found

    Most of the users of EMF (developers or adopters alike) have seen this message at least once; and most probably wondered what was going on :).

    This error message indicates that the package which NsURI is '*' hasn't been registered in the Package registry. Most of the time, that means you either a) launched your program standalone and didn't register the package correctly or b) you haven't installed the plug-in that provides that metamodel.

    With Acceleo, encountering this exception is even more likely than normal, as the launch configuration for our generations allow for both standalone or plug-in generations. Selecting standalone without registering the needed package will inevitably lead to this error.

    Solving it is easy : it is a matter of registering the needed packages (and, optionally, resource factories). How is it done? Here is the most classic example with UML :

    Package with uri 'http://www.eclipse.org/uml2/2.1.0/UML' not found.
    What do I need to do for my UML model to be loadable :
    EPackage.Registry.INSTANCE.put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE);
    Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(UMLResource.FILE_EXTENSION, UMLResource.Factory.INSTANCE);

    The same goes for every metamodel you might need, simply change UMLPackage by XxxPackage according to your metamodel. The Resource Factory is mandatory for UML, but your metamodel might not need one (in that case you can ignore this line if you don't have a custom factory.) For loading an XMI model, use:

    Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()");

    For loading from a ResourceSet, use code similar to the following:

    ResourceSet rset = new ResourceSetImpl();
    rset.getPackageRegistry().put(DealerPackage.eNS_URI, DealerPackage.eINSTANCE);
    rset.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl());
    Resource resource = rset.createResource(URI.createFileURI("/path/to/model/YahooDealer.xmi"));
    resource.load(null);
    dealer = (YahooDealerImpl)resource.getContents().get(0);
    ...

    The different is the calls to the global Package Registry and Resource Factory Registry are replaced with the local registries provided by the ResourceSet.

    You need these two lines to be before the point where your model is loaded. For Acceleo, this is done in the generated Java launcher : simply change the implementation of the registerPackages and registerResourceFactories method to add these needed lines.

    The above post is copied mostly verbatim from  http://eclipsemde.blogspot.com/2010/06/package-with-uri-not-found.html

    UPDATE: It wouldn't be cool if I haven't added something else, so here goes...

    In an Eclipse application (non-standalone Java), EMF registers packages using extension point named org.eclipse.emf.ecore.generated_package (I've searched but unfortunately, the extension point reference documentation for this seems unavailable from the web!) that your plugin declares.

    A sample plugin.xml that declares two EMF model packages:

       <extension point="org.eclipse.emf.ecore.generated_package">
          <package
                class="com.abispulsa.AbispulsaPackage"
                genModel="model/abispulsa.genmodel"
                uri="http://www.abispulsa.com/model/1.0"/>
          <package
                class="com.abispulsa.dealer.DealerPackage"
                genModel="model/AbispulsaDealer.genmodel"
                uri="http://www.abispulsa.com/dealer/1.0">
          </package>
       </extension>

    This would be useful for readers unfamiliar with EMF to know where the package registration is done.

    Especially if you change your metamodel's nsURI, you have to the change the registration here too. (and also the edit and editors if you have generated them... check out the plugin.xml files on the respective plugins.)