启用EMF的自动生成UUID功能

缺省条件下,EMF不会为每个对象维护一个唯一的id,这在一些情况下不太方便,例如要在web环境下编辑一个EMF模型实例,通常需要在URL里传递对象的id以便确定目标。

有两种方式解决这类问题。第一种是在模型里给每个对象增加一个id属性,并维护使其保持唯一,例如在XXXFactorycreateXXX()方法里为这个属性赋值。

另一种方式是借助EMF的功能。其实EMF的XMIResource维护了一个id与对象的映射表,并且会为每个新创建的对象自动赋值,只要稍微设置一下就可以启用这个功能,具体的方法如下。

  1. xxx.genmodel里选中package节点,在属性视图里把Resource Type属性的值设置为XMI

file

  1. 重新生成代码,EMF生成的代码里会比原来多出XXXResourceImpl.java文件。

  2. XXXResourceImpl.java里,覆盖useUUIDs()方法,让其返回true(缺省返回的是false)。

  3. 重新生成代码。(删除以前的Manifest.MFplugin.xml文件以便这两个文件能得到更新)

这样,如果用文本方式查看新创建的模型实例文件,会发现每个对象都多出一个xmi:id属性,如xmi:id="_gitpslkoEd2PdI7FPnuunA"。要在程序里使用这个id,只需要用resource.getId(eobject)即可得到,其中resource可以通过eobject.eResource()很方便的获得。

参考链接

How To Enable UUID In EMF Generated Model To Get Copy&Paste Working
这个链接里的方法太麻烦了,但功效一样。

Eclipse GMF - Enabling UUIDs in Semantic model - The Simplest Way
GMF的时候这个链接里的方法应该比较方便

利用OSGi DS实现可配置Web应用程序初探

Eclipse的插件体系结构让我们可以灵活定义插件,组装成可插拔的软件系统,OSGi的Declarative Services(DS)有着和Eclipse扩展点类似的思想(见很详细的一篇对比文章),很自然的想到,如果把DS应用在Web应用程序上,我们将能够通过定义自己的“扩展点”,打造SOA的Web应用程序。题目中“可配置”是指,根据用户需求,提供给客户不同的包即可形成针对该客户定制的产品/解决方案。

现在来试试怎样用DS实现动态配置一个Web应用程序界面里菜单,使用的OSGi实现还是Equinox。Eclipse里菜单项是通过实现actionSets、editorActions等等扩展点添加的,在OSGi里没有这些“扩展点”,没关系,我们可以自己定义。

一、用一个Bundle定义Java接口文件,和Eclipse扩展点的功能类似,这些接口可以作为服务的接入点。例子里这个Bundle的ID是net.bjzhanghao.osgi.services,接口类是IMenuContributor,内容很简单如下所示:

public interface IMenuContributor {
    public List<MenuItem> getItems();
}

上面用到的MenuItem是自己定义的一个简单的数据结构,包含name和url两个String类型的成员变量和相应的getter/setter方法,这里就不展示了。

二、用0..n个Bundle实现上面定义的接口(暂时称之为Contributor),并声明为服务。例子里有两个这样的Bundle,ID分别是net.bjzhanghao.osgi.menu.contributor和net.bjzhanghao.osgi.menu.contributor2,实现类都是MyMenuContributor,前者提供了Menu1..3,后者提供了Menu4..5;以下是服务的声明,即项目里OSGI-INF/component.xml文件的内容:

<?xml version="1.0" encoding="UTF-8"?>
<component name="services">
    <implementation 
        class="net.bjzhanghao.osgi.menu.contributor.MyMenuContributor"/> 
    <service>
        <provide interface="net.bjzhanghao.osgi.services.IMenuContributor"/>
    </service>
</component>

MyMenuContributor实现IMenuContributor,你可以任意实现它,例子里是让它提供三个菜单项:

public class MyMenuContributor implements IMenuContributor {
    public List<MenuItem> getItems() {
        List<MenuItem> list = new ArrayList<MenuItem>();
        list.add(new MenuItem("Menu1", null));
        list.add(new MenuItem("Menu2", null));
        list.add(new MenuItem("Menu3", null));
        return list;
    }
}

三、现在SOA的一半工作也就是注册服务的部分完成了,剩下要做的是另一半,消费这些服务。首先在web应用程序Bundle里,创建一个Helper类收集这些服务,这个Helper类的功能相当于一个Registry。例子里这个Bundle的ID是net.bjzhanghao.osgi.example,Helper类是MenuHelper;以下是对MenuHelper的配置,即项目里OSGI-INF/component.xml文件的内容,这样OSGi在启动Bundle时会把实现IMenuContributor的服务通过addMenuContributor方法注册到MenuHelper里,注意cardinality的值要为"0..n",policy的值要为"dynamic":

<?xml version="1.0" encoding="UTF-8"?>
<component name="menuHelper">
    <implementation     
        class="net.bjzhanghao.osgi.example.MenuHelper"/>   
    <reference name="menuHelper"
        interface="net.bjzhanghao.osgi.services.IMenuContributor"
        cardinality="0..n"
        policy="dynamic"
        bind="addMenuContributor"
        unbind="removeMenuContributor"
    />
</component>

四、在JSP/Servlet里,利用上面的Helper类构造界面(或业务逻辑)。因为我们定义的接口是关于菜单的,所以例子应用的菜单会根据Bundle配置(启动/停止)变化。例子里是在ExampleServlet里实现的,代码很简单如下所示:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html");
    resp.getWriter().write("Hello<br/>");
    resp.getWriter().write("<ul>");
    for (IMenuContributor menuContributor : MenuHelper.getInstance().getMenuContributors()) {
        for (MenuItem mItem : menuContributor.getItems()) {
            resp.getWriter().write("<li><a href=\"" + mItem.getUrl() + "\">" + mItem.getName() + "</a></li>");
        }
    }
    resp.getWriter().write("</ul>");
}

五、执行方式:

1、在Eclipse里导入代码包里所有项目,启动上述所有项目及其依赖的项目(Add Required Bundles),见下图:

file

2、在浏览器里输入地址http://localhost/exampleServlet,应该可以看到五个菜单项,见下图(这个简单的例子里没有考虑顺序问题,所以Menu4..5可能出现在前面,使用类似Eclipse里为Menu预留位置的方式可以解决):

file

3、回到Eclipse,在Console里输入ss查看当前的Bundle,应该可以看到类似下图的内容:

file

4、在Console里输入stop 35(35是Contributor的Bundle序号),Console会提示“Calling removeMenuContributor”,然后到浏览器里刷新页面,应该看到菜单项只剩下Menu4..5。

file

代码下载

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/11/21/967320.html

Equinox OSGi服务器应用程序的配置步骤

file

本文介绍在Eclipse里如何配置一个简单的基于Eclipse Equinox OSGi实现的Web应用程序,在它的基础上可以构造更加复杂的应用,本文使用的是Eclipse 3.3.1版本,如果你的Eclipse版本在3.2.0或以上应该都可以。

一、支持静态页面和Servlet

  1. 创建一个新的plugin项目, net.bjzhanghao.osgi.test,在向导第一步里选中“This plug-in is target,在下一步的“Plug-in Options”里选中“Generate an activator”。

file

  1. 在例子项目的MANIFEST.MF里添加如下依赖项目,这些项目都是Eclipse自带的:
org.eclipse.equinox.http.jetty
org.eclipse.equinox.http.servlet
org.mortbay.jetty
org.apache.commons.logging
javax.servlet
org.eclipse.equinox.http.registry
  1. 在例子项目根目录下创建一个放置web文件的目录,如“web_files”,在这个目录下写一个简单的index.html文件。

  2. 为项目建一个plugin.xml文件,内容如下:

<plugin>
  <extension point="org.eclipse.equinox.http.registry.resources">
    <resource alias="/web" base-name="/web_files"/>
  </extension>
</plugin>

注意,这时若MANIFEST.MF里提示错误,只要在Bundle-SymbolicName这一行后面加上;singleton:=true即可解决。

  1. 现在可以启动这个应用程序了。在Eclipse菜单里选择Run->Open Run Dialog...,在左边的 OSGi Framework项下创建一个新的启动配置项,在右边先点Deselect All清空所有复选框,然后在Workspace下选中 自己的osgi项目,再点Add Required Bundles按钮,Eclipse会自动把所依赖的项目选中。 最后按Debug按钮启动,内嵌的jetty和我们的项目会一起被启动。

file

  1. 打开浏览器,输入 http://localhost/web/index.html 应该可以看到index.html里的内容。

以上只验证了静态页面,现在来配置一个servlet看看。

  1. 在项目里创建一个继承自HttpServlet的类,覆盖doGet()方法,内容是在网页上打印一些文本。

  2. 在项目的plugin.xml里添加下面的内容,这些内容指定了servlet的访问路径和实现类:

<extension point="org.eclipse.equinox.http.registry.servlets">
    <servlet
      alias="/exampleServlet"
      class="net.bjzhanghao.osgi.example.servlet.ExampleServlet"/>
</extension>
  1. 重新启动项目,在浏览器里输入 http://localhost/exampleServlet ,应该可以看到servlet的输出。

二、支持JSP页面

  1. 在index.html所在目录下创建一个简单的jsp文件index.jsp

  2. 打开项目的MANIFEST.MF文件,添加如下项目依赖:

org.eclipse.equinox.jsp.jasper,
org.apache.jasper,
org.eclipse.equinox.jsp.jasper.registry,
javax.servlet.jsp,
org.apache.commons.el,
org.eclipse.equinox.http.helper,
org.eclipse.osgi,
org.eclipse.osgi.services

其中org.eclipse.equinox.http.helper需要从cvs里下载得到(目前是在/cvsroot/eclipse下的 equinox-incubator目录里,以后可能会直接放到/cvsroot/eclipse下)。

  1. 修改Activator,目的是注册一个处理扩展名为.jsp类型的servlet,感觉这一步以后应该有更简单的方法,例如通过扩展点。
public class Activator implements BundleActivator {

    private ServiceTracker httpServiceTracker;

    String jspContext = "/jsps";
    String jspFolder = "/web_files";

    public void start(BundleContext context) throws Exception {
        httpServiceTracker = new HttpServiceTracker(context);
        httpServiceTracker.open();
    }

    public void stop(BundleContext context) throws Exception {
        httpServiceTracker.open();
    }

    private class HttpServiceTracker extends ServiceTracker {

        public HttpServiceTracker(BundleContext context) {
            super(context, HttpService.class.getName(), null);
        }

        public Object addingService(ServiceReference reference) {
            final HttpService httpService = (HttpService) context
                    .getService(reference);
            try {
                HttpContext commonContext = new BundleEntryHttpContext(context
                        .getBundle(), jspFolder);
                httpService.registerResources(jspContext, "/", commonContext);

                Servlet adaptedJspServlet = new ContextPathServletAdaptor(
                        new JspServlet(context.getBundle(), jspFolder),
                        jspContext);
                httpService.registerServlet(jspContext + "/*.jsp",
                        adaptedJspServlet, null, commonContext);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return httpService;
        }

        public void removedService(ServiceReference reference, Object service) {
            final HttpService httpService = (HttpService) service;
            httpService.unregister(jspContext);
            httpService.unregister(jspContext + "/*.jsp");
            super.removedService(reference, service);
        }
    }
}
  1. 打开Debug对话框,选中workspace里的例子osgi项目和org.eclipse.equinox.http.helper项目,再按Add Required Bundles按钮,然后启动程序。

  2. 在浏览器里输入 http://localhost/jsps/index.jsp ,应该可以看到jsp输出。

例子项目源代码下载(链接)。

参考链接:

Embedding an HTTP server in Equinox
Writing a bundle-based server application
OSGi based JSP Support

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/10/28/940622.html

gnujaxp.jar与axis冲突问题

一个Web应用程序,在WTP里无法完成web services向导,手工配置axis后访问http://localhost:8080/myapp/servlet/AxisServlet 时提示如下错误:

org.apache.axis.InternalException: org.apache.axis.ConfigurationException: org.apache.axis.deployment.wsdd.WSDDException: Must include type attribute for Handler deployment!
org.apache.axis.deployment.wsdd.WSDDException: Must include type attribute for Handler deployment!
    at org.apache.axis.deployment.wsdd.WSDDHandler.<init>(WSDDHandler.java:50)
    at org.apache.axis.deployment.wsdd.WSDDDeployment.<init>(WSDDDeployment.java:176)
    at org.apache.axis.deployment.wsdd.WSDDDocument.setDocument(WSDDDocument.java:139)
    at org.apache.axis.deployment.wsdd.WSDDDocument.<init>(WSDDDocument.java:65)
    at org.apache.axis.configuration.FileProvider.configureEngine(FileProvider.java:179)
    at org.apache.axis.AxisEngine.init(AxisEngine.java:172)
    at org.apache.axis.AxisEngine.<init>(AxisEngine.java:156)
    at org.apache.axis.server.AxisServer.<init>(AxisServer.java:88)
    at org.apache.axis.server.DefaultAxisServerFactory.createServer(DefaultAxisServerFactory.java:109)
    at org.apache.axis.server.DefaultAxisServerFactory.getServer(DefaultAxisServerFactory.java:73)
    at org.apache.axis.server.AxisServer.getServer(AxisServer.java:73)
    at org.apache.axis.transport.http.AxisServletBase.getEngine(AxisServletBase.java:185)
    at org.apache.axis.transport.http.AxisServletBase.getOption(AxisServletBase.java:396)
    at org.apache.axis.transport.http.AxisServletBase.init(AxisServletBase.java:112)
    at org.apache.axis.transport.http.AxisServlet.init(AxisServlet.java:156)
    at javax.servlet.GenericServlet.init(GenericServlet.java:211)

经排除法发现只要删除WEB-INF/lib下的gnujaxp.jar即恢复正常。这个jar文件是因为应用程序使用到jfreechart带来的,按照jfree.org论坛里的说法,只有使用jre1.3.1版本jfreechart才真正需要此文件,所以删掉它问题解决,WTP里web services向导恢复正常。

BTW, 除了axis,这个gnujaxp.jar好像还和spring、ibatis等环境有冲突,见这个google查询结果

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/08/17/859449.html

WSRP调用中的若干问题和解决

1、在soapui里调用远程getMarkup()方法,http头里无法包含cookie信息

解决方法:用soapui把soap消息发送至tcpmon,然后在tcpmon里修改http消息(添加cookie段),再重新发送。

2、安装wtp后eclipse里不出现相应功能

解决方法:安装JEM-SDK,它是wtp的先决条件。其他先决条件还有EMF SDK和GEF SDK。

3、从WSRP的WSDL生成Java代码后,访问getMarkup()方法提示InvalidCookie

解决方法:先访问initCookie()方法,再访问getMarkup()。注意要调用Service#setMaintainSession(true)以保证程序对getMarkup()的调用会在http头里加入了cookie段。

4、Websphere Portal的WSRP的WSDL

解决方法:http://host:3333/wps/wsdl/wsrp_service.wsdl

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/04/11/709654.html

使用Java JNI的步骤

  1. 写Java类,其中定义了native方法
public class WitWrapper {

    static {
        System.loadLibrary("witengine");
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        new WitWrapper().run();
    }

    private void run() {
        solve("C:\\temp\\witlib\\problem.txt");
    }

    public native static int solve(String filename);
}
  1. 在命令行下用javah生成.h文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class XXX_WitWrapper */

#ifndef _Included_XXX_witwrapper_WitWrapper
#define _Included_XXX_witwrapper_WitWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     XXX_WitWrapper
 * Method:    solve
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_XXX_WitWrapper_solve
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif 
  1. 复制.h文件到一个vc++6.0的dll工程里,用vs2005得到的dll会依赖msvcr80d.dll等其他dll,不建议。把$jdk_dir$/include里的jni.h和$jdk_dir$/include/win32里的jni_md.h也添加到这个工程里。

  2. 按.h文件实现.c文件对应的方法,如下例,注意jstring类型要转换成char *类型,否则即使英文也会有乱码:

/**************************************************************************** 
 *
 * Sample WIT API Program.
 * Runs implosion on the Diner problem.
 *
 ****************************************************************************/
#include <stdlib.h>
#include "wit.h"
#include "jni.h"

/****************************************************************************/
/* Main Program                                                             */
/****************************************************************************/
JNIEXPORT jint JNICALL Java_XXX_WitWrapper_solve
  (JNIEnv * env, jclass jc, jstring file)
{
  WitRun * theWitRun;
  const char *str = (*env)->GetStringUTFChars(env, file, 0);//把jstring转换为char *,否则会有错
  printf(str);

   /* Initialize WIT */
   witNewRun( &theWitRun );
   witInitialize ( theWitRun );

   witReadData (theWitRun, str);
   /*************************************************************************
    *
    * Finished entering data
    *
    ************************************************************************/

   witOptImplode( theWitRun );
   witWriteExecSched( theWitRun, "execsched.txt", WitBSV );
   witWriteShipSched( theWitRun, "shipsched.txt", WitBSV );

   witDeleteRun( theWitRun );

   exit (0);

} /* main */
  1. 编译生成.dll文件,把它和其他依赖的文件放在path环境变量包含的一个目录下,在java里就可以调用了。注意调用这个dll的java类名(包括所在包)不能改,否则会出现UnsatisfiedLinkException,如果一定要改名,只能重新生成一遍dll了。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/03/30/693889.html

关于WSRP服务的异步调用

Portal可以将不同的Web应用聚合在同一页面,而Web Services for Remote Portlets (WSRP)规范允许Portal聚合远程Portal Server提供的Portlet,在这种关系下,远程Portal被称为Producer,本地Portal被称为Consumer,最终用户只访问Consumer,并不需要了解Producer的存在。WSRP和普通Web Services比起来更容易使用,因为界面部分已经由Producer提供了,我们只要在Consumer里做一些设置而不是编写JSP代码。

听起来不错,但为此我们也许要付出些代价,其中我想最突出的是性能问题。因为Consumer在向最终用户返回聚合后的页面前,必须等待所有Portlet包括远程Portlet返回结果,这样,只要有一个Portlet响应时间较长,整个页面就变得很慢。在使用WSRP时,用户到Consumer一般是远程访问,而Consumer到Producer也是远程的,这必然带来额外的时间开销。

一个可能的解决方法是在Consumer里异步显示每个Portlet(或仅异步显示那些远程Portlet),AJAX技术听起来是一个不错的选择,lao_lee的这篇文章里曾提到他们有过类似的想法:

05年时我们曾经的一个idea是,把每一个portlet封装为一个web service服务点, (WSRP已经可以作到这一点), 然后改进聚合器,让聚合器首先把页面的框架和js返回,然后每一个portlet通过AJAX请求异步拿到自己的内容. 这里涉及到一个关键问题是URL改写.

我想除了URL改写以外还有些问题必须处理。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/12/27/605510.html

EMF里EReference的containment和container属性

EReference的containment属性如果为true,表示目标EClass是被源EClass包含的,这是一种十分强的关系,例如汽车和车轮的关系。在ecore里,不允许包含关系形成圈,也就是说如果A包含B,B就不能再直接或间接的包含A;另外,如果作为容器的对象结束了自己的生命周期,被它包含的对象也将结束自己的生命周期。

如果一个EReference有作为EOpposite的EReference,并且后者的containment属性为true,则这个EReference的container属性为true。换句话说,一个EReference的container属性表示它指向的EClass是否为包含者。

public boolean isContainer()
{
  EReference theOpposite = getEOpposite();
  return theOpposite != null && theOpposite.isContainment();
}

最后,container属性是derived属性,所以在ecore编辑器里我们无法直接编辑一个EReference的这个属性。

参考资料

http://dev.eclipse.org/newslists/news.eclipse.tools.emf/msg01448.html

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/11/20/566556.html

关于实现StarGen的思考记录

毕业设计的内容是Web应用程序的代码生成器,因为接触emf有一段时间了,我觉得用emf完全可以很方便的实现这个程序。这是更全面了解emf特别是codegen部分的一个好机会。这个帖子将记录这个过程的点滴,所以会比较琐碎,也许这些文字能被用在毕业论文里(不希望论文里都是google来的东西)。我发现cnblogs的帖子被修改后在rss里会重新出现,所以订阅我的blog的读者可能要经常被“骚扰”了:P

为了方便起见,我给这个代码生成器起了一个名字叫做“StarGen”,完全是一个代号,不包含任何意义。而且很幸运,这个名字在sourceforge.net上没有被注册过,等有了时间我要申请一下,所以程序里的包名都先以net.sf.stargen开头了。(请不要和我抢注这个名字啊,否则光refactor的工作就不少了,也许我就不开源了。)

闲话少说,以下是在实现StarGen过程中对一些问题的思考:

1、(3-24)首先我从使用者的角度审视一下这个应用程序完成后应该具有的功能,StarGen的使用者其实就是Web应用的开发者,他通过StarGen可以定义一些元素,这些元素将被转换为代码。粗略想了一下,可以定义的元素包括Class、Attribute、Operation、Reference等等,很像是一张类图。每个类将被转换为Web应用的一个模块,包含增、删、改、查询、列表、查看这些功能,这些功能分别由一个或多个jsp页面和Struts的Action(目前考虑生成基于Struts和Hibernate的应用,因为实验室一直使用它们)。

那么是否有了类图就够了呢,这样只能实现简单的数据操作,基本没有什么业务逻辑在里面。为了能让生成的Web应用具有逻辑性,我初步为StarGen的输入除类图以外增加了一个流程图,流程图里定义的流程会在生成的代码里得到体现。这个图和工作流里的那种图还是要有所区别的,我以前开发过工作流程序,在流程引擎里都要处理条件、会签、回退等情况,实际上是非常复杂的。因此流程这部分还需要花时间考虑一下怎样简化比较合适。

file
图1 初步的StarGen使用步骤(六个步骤)

2、(3-24)既然明确了StarGen的输入,一个是类图,另一个是暂时称作流程图的东西,那么使用者应该怎样提供这些输入呢?首先看一下类图方面,这里使用者至少要能够定义出应用里应该有哪些对象类型,它们的成员变量、方法等等,还有类与类之间的联系。如果说他(使用者)定义的是一个模型,那么我首先要有一个元模型,元模型好比模型的语言,在元模型里我要定义“类”、“成员变量”、“方法”等等这些概念。按照这个想法,我想到平时使用emf建模时用的“语言”不就是ecore吗,没错,ecore是一个元模型,和uml一样,都可以用来定义模型。那么我可不可以就让使用者直接用ecore建模呢,因为使用ecore建模的工具已经有不少了,最起码也有随emf一起来的“Sample Ecore Model Editor”,想图形化可以用EclipseUML或者gmf的ecore editor都行,这样我可以节约很多工作量。

3、(3-24)图1中步骤3和步骤5是StarGen的主要工作所在。就像emf里有genmodel一样,StarGen也需要有自己的genmodel,不妨暂时称作“StarModel”吧。使用者输入的两个model被合并为一个starmodel,在starmodel里包含了它们的全部信息,此外还有一些如包名、类名这样的信息,这些信息和Web应用的业务逻辑没有什么关系。生成starmodel的部分我将主要org.eclipse.emf.importer这个项目,它可以从包括ecore模型在内的几种模型里生成genmodel。

另一项重要工作就是从starmodel生成可以执行的代码,这里我将参考以org.eclipse.emf.codegen开头的四个项目,特别是Generator类,我会从它“开刀”看看有什么值得利用。另外org.eclipse.uml2.codegen开头的几个项目也值得一看,uml2的代码生成借用了很多emf的东西,例如uml2的genmodel就完全是从emf的genmodel继承来的,只是增加了一些新的元素,我可能也会采取这种方式而非从头开始定义一个和emf的genmodel类似的starmodel。BTW,我觉得这不是走捷径,这叫“Leverage”!

4、(3-27)在emf里,从ecore生成genmodel的主要工作是在EcoreImporter中完成的,这个类继承ModelImporter类。在模型导入向导ModelImporterWizard的performFinish()方法里会调用prepareGenModelAndEPackages()和saveGenModelAndEPackages()方法,前者负责构造genmodel实例,后者负责保存这个实例。

ModelImporter的prepareGenModelAndEPackages()方法的主要过程是这样的:计算这个genmodel引用到的其他genmodel,将这些genmodel连同自己都加到同一个Resource内,调用genmodel的initialize()方法,这个方法会导致genmodel里的GenPackage的initialize()方法被调用,再导致GenClassifier的initialize()方法被调用,如此类推,直到整个genmodel构造完毕。然后,通过调用traverseGenPackages()和adjustGenModel()对得到的genmodel做一些调整,主要是一些属性的赋值,traverseGenPackages()的作用还不太明白。最后,调用reconcile()方法将原来的genmodel里被修改过的值应用到新生成的genmodel上,否则用户在Genmodel Editor里所做的修改在reload一次后就丢失了。

ModelImporter的saveGenModelAndEPackages()则十分直白,它会判断是否需要新建project,以及根据需要reference的其他genmodel决定在project里增加对哪些project的依赖(一般情况下好像都是在已有project里建立genmodel的),然后对需要保存的resource调用Resource#save()即可。

对于StarGen来说,保存的那部分应该是一样的,主要的修改会在生成genmodel的prepareGenModelAndEPackages()方法里,因为StarGen的genmodel(StarModel)在emf的genmodel基础上增加了Process等类,所以很可能需要在StarPackage的initialize()里对它们进行处理,这通过覆盖GenPackage()的initialize()方法就可以实现。

5、(3-29)EMF Model的新建向导(File->New->Others->Eclipse Modeling Framework->EMF Model)对应的类是EMFModelWizard,整个向导由两部分构成,第一部分是前两页,第一页叫NewGenModelFileCreationPage,在这里用户指定.genmodel文件的名字和所在项目,第二页是一个ModelConverterDescriptorSelectionPage的子类,它搜索注册到Eclipse上的org.eclipse.emf.importer.modelImporterDescriptors扩展点提供一个ModelImporter列表,例如EcoreImporter、RoseImporter等等。注意,在ModelConverterDescriptorSelectionPage的getNextPage()方法里会决定使用哪个Wizard作为向导第二部分,同时会调用adjustModelConverterWizard()方法做一些赋值工作。

向导的第二部分就由Importer决定,对于Ecore的情况对应EcoreImporterWizard,它是ModelImporterWizard的子类,包含两个向导页ModelImporterDetailPage和ModelImporterPackagePage,分别让用户选择一个.ecore文件和选择要生成或引用的包。ModelImporterWizard的子类都要实现createModelConverter()方法,返回一个ModelConverter类的实例,例如return new EcoreImporter(),EcoreImporter是ModelImporter的子类,ModelImporter则是ModelConverter的子类。

向导负责收集必要的信息,具体的生成.genmodel文件的工作在ModelImporter里完成,主要的步骤就是前面说过的prepareGenModelAndEPackages()和saveGenModelAndEPackages()方法。

6、(3-30)为了实现StarGen区别于emf的功能(流程等等),我“继承”emf的genmodel模型(genmodel.ecore)构造了StarGen的模型(stargen.ecore),这个过程参考了uml2对codegen的实现。StarGen的genmodel如下图所示:

需要注意的是,图中很多元素都要继承两个类,一个是emf中的GenXXX,另一个可能是StarBase,StarBase是GenBase的子类。这时要把GenXXX放在继承列表的第一位,否则生成的代码会缺少一些方法如getName()和reconcil()而无法编译通过。另外一点要注意的就是,在genmodel.genmodel(由genmodel.ecore生成,位于org.eclipse.emf.codegen.ecore/model)中,GenModel的Creation Commands属性是false,继承的stargen.genmodel里也要做同样的设置,否则在生成的editor里会出现MissingResourceException。不过,这样设置以后editor里就没有New Child/Sibling菜单选项了。

7、(3-31)初步使用自己的Template生成代码。实际上emf从大约2.2.0开始支持了Dynamic Template,只要把genmodel的“Dynamic Templates”属性设置为true,并在适当的位置放置另外的Template即可按这个Template生成代码。不过,为了更大的灵活性以及我暂时没有想到的情况,我没有使用这种机制。在emf的GenClassImpl这个类里有很多getXXXEmitter()方法,它的返回值决定了genmodel里各个元素使用哪个模板,例如GenClassImpl的getFactoryClassEmitter()是这样写的:

 public JETEmitter getFactoryClassEmitter()
  {
    if (factoryClassEmitter == null)
    {
      factoryClassEmitter = createJETEmitter(factoryClassTemplateName);
      setMethod(factoryClassEmitter, "org.eclipse.emf.codegen.ecore.templates.model.FactoryClass");
    }
    return factoryClassEmitter;
  } 

可以看到emf生成Factory类是通过org.eclipse.emf.codegen.ecore.templates.model.FactoryClass这个(经过编译的)模板完成的,如果想使用自己的模板,只要在StarModelImpl类里覆盖这个方法(StarModelImpl是GenModelImpl的子类,参考前面stargen的模型图),把setMethod()的参数改为"net.sf.stargen.templates.FactoryClass"即可。

模板是一些.javajet文件,在一个具有jet nature的project里,你写的所有.XXXjet文件在保存时都会被编译为一个.java类,该类的generate()方法生成.XXX文件的内容。emf的那些.javajet文件的内容还是比较复杂的,需要花些时间看看怎么利用,其中最主要是Class.javajet,因为StarGen不会去生成.edit和.editor的代码。再接下来就是要构造与Web应用有关的Template了,例如Action.javajet或是Mapping.hbm.xmljet等等,工作量也许很大,但一些问题应该可以先简化处理。

8、(4-5)现在StarGen可以从.ecore文件生成.starmodel文件了,并且可以生成一个Factory和n个bean-like类(n=ecore里EClass的数量),我对emf的Class.javajet做了巨大的简化,代码从1800多行减为不到100行,汗,真难想象emf的Class.javajet当时是怎样写出来的。下一步的工作是生成struts的配置文件,这里有一个之前没有考虑到的困难,那就是在xml文件里怎样控制jmerge的行为,应该与在java类里有所不同,还是要学习一下。

9、(4-10)生成.jsp文件的时候遇到一些困难。一是jsp与jet的控制标签都是"<% %>",必须在jet模板里指定使用不同的标签,例如startTag="<$" endTag="$>";二是GenBaseImpl类提供了多个generate()方法,用于生成.java文件的那个generate()方法不能用于生成.jsp文件,因为输出的文件名的扩展名在方法里是写死为".java"的,要使用具有五个参数的那个generate()方法,并自己计算输出文件的全名。

10、(4-11)StarGen生成如下代码:

模型元素 生成的代码
StarModel web.xml, hibernate.properties
StarPackage Factory, struts-config.xml, Resource.properties(4-17), IndexAction(4-15), index.jsp(4-15)
StarClass Bean-like Class/Enum(4-18), Helper(4-14新增), XXXAction, XXXForm, JSPs(List, Create, Edit), struts-config-xxx.xml, ApplicationResources.properties, xxx.hbm.xml, validation.xml(4-18)

接下来要花几天的时间对模板内容进行调整,这样就可以初步得到可以运行的生成结果。(PS.昨天看到一个很像的代码生成工具Modelstry,也是生成基于struts的web应用,还利用了spring来管理hibernate。但它好像不是用jet生成的代码,不知道merge方面是否有问题。刚下载了试用版本,有时间研究一下,取长补短。)

11、(4-13)生成了可以列表和添加新记录的web应用程序。第三方类库,包括Struts和Hibernate用到的那些jar包和tld文件,则需要用户手动添加到项目。GenFeature有一个方法是getGetAccessor(),得到的是bean里的getter方法名字,但却没有getSetAccessor(),为什么呢,因为getter不一定是以"get"开头的,还有"is"开头的情况,所以在getGetAccessor()里做了处理,而需要setXXX方法的地方直接写set<%=genFeature.getAccessorName()%>即可。

12、(4-14)实现了简单类的编辑和删除,jsp和action。为每个ecore类新生成一个Helper类,功能类似emf生成的ItemProvider,目前主要提供getText()方法,用户可以修改这个方法显示需要的内容。缺省的这个类返回<%=genClass.getSafeUncapName()%>.<%=genClass.getLabelFeature().getGetAccessor()%>()。

13、测试用的ecore模型,十分简单。

file

14、(4-17)处理了jsp页面里用checkbox显示GenFeature#isBooleanType()返回true的类型,在StarFeature里增加了password布尔变量,此值为true的用代替。非String类型的GenFeature,如int型和boolean型,不需特殊处理即可正常使用,原因是在ActionForm里已经是这种类型,本来担心的在Action的postCreate()/postEdit()方法里需要parse的问题并不存在。

15、(4-18)对特殊的GenFeature(即不是普通String类型的属性)做如下处理:

GenFeature情况 处理方法
transient 在hibernate映射文件里去掉对这类属性的声明
volatile 待处理
boolean 在create.jsp/edit.jsp页面中以checkbox展示
contains 在container的编辑和查看页面中增加一个创建child的link
ListType 在create.jsp/edit.jsp页面中以multiple的select展示,在hibernate映射文件里使用代替
reference   在hibernate映射文件里使用代替
MapType 这一版本暂时不作处理
Bidirectional 在hibernate映射文件里设置,在一方的column名称与一方的column保持一致
EnumType 假设GenFeature名为location,在模型类里增加一个int型的locationValue属性,mapping里只对这个属性持久化,而getEnum()里通过locationValue计算得到Enum类型返回值。生成继承AbstractEnumerator的类,生成一个Resource文件(为了对Enumerator的Literals实现国际化),在具有Enum类型成员变量的GenClass生成的struts-config-xxx.xml里要引用这个Resource文件。在Create.jsp/Edit.jsp里用下拉框展示,View.jsp/List.jsp里显示Resource里对应的文字内容(而非直接显示Literal),ActionForm里与模型类相对应,要有locationValue属性。(4-19)
notNull 在hibernate映射文件里设置 ,在validation.xml文件里增加required约束
其他非String类型 在validation.xml文件里增加相应约束

16、(4-18)GenFeature常用的方法:getTypeGenClass()得到与GenFeature关联的“对面”的GenClass;

17、(4-19)让生成的java代码能够拥有适当的import声明的方法:在jet文件前部增加类似下面的语句:

<%genModel.addImport("java.util.Arrays");%>
<%genModel.addImport("java.util.List");%>
<%genModel.addImport("java.util.Collections");%>
<%genModel.markImportLocation(stringBuffer, genPackage);%>

再在jet文件最后一行增加<%genModel.emitSortedImports();%>这句即可。

 18、(4-19)实现了对Enum类型GenFeature的处理,但生成的代码并不完美,在JSP页面里有类似下面的语句(因为用struts的标签无法很方便的表示,所以用

<html:select property="locationValue">
    <%for (Iterator iter = Location.VALUES.iterator(); iter.hasNext();) {
        Location location = (Location) iter.next();%>
    <option value="<%=location.getValue()%>"
        <bean:message key="<%=location.getName()%>" bundle="location"/>
    </option>
    <%}%>
</html:select>

19、(4-20)生成的列表支持排序了。下面要处理的是分页问题,会稍微麻烦一些。

20、(5-10)针对流程的设计:流程(Process)由活动(Activity)组成,每个活动可以等价为对数据库的一个操作,例如增加一个用户就是在用户表里增加一条记录;又因为使用了Hibernate,所以对数据库的操作就是对持久对象的操作,例如增加用户就是增加一个User对象。

为了简化实现,暂时不考虑在流程中引入条件。

每个活动应该具有以下属性:

  • 操作(Operation),枚举类型,可选值为Select、Create、Delete、Modify之一;在实现中也可以使用活动不同的子类达到同样目的。
  • 目标类型(TargetType),它的值是数据模型里的一个类,例如User、Product,等等;因此流程模型有对数据模型的依赖。
  • 目标对象(TargetObject),该活动中处理的实例,例如用户张三;

活动间目标对象的传递问题:例如第一个活动创建了Product1,第二个活动的内容是编辑Product1的属性,在流程设计中,如何告知第二个活动Product1的ID号?另外,会不会存在有多个ID号需要告知的情况?现在的想法是在session里维护这个对象,Select操作为这个对象赋值,流程结束时对其清空。

21、(5-11)因为流程里没有分支结构,所以Begin和End这两个Activity似乎没有必要存在了。今天完成了StarFlowModel的原始编辑器,用它创建的流程模型可以通过“Load Resources...”载入数据模型,并建立两个模型之间的关联。同时删除了原来设计在数据模型里的Process和Activity这两个类型。

接下来要考虑具体的流程模板内容了。

22、(5-13)基本完成了Select和Modify这两个活动的模板,利用它们可以组成“修改产品信息”这样的简单流程。剩下的活动类型还有Create、Delete和Custom这几种,其中Custom将生成没有任何功能的代码框架,让用户自己实现任意功能。

另外,流程对应的模板也已经完成了。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/03/24/357784.html

感受Ruby on Rails

最近看到几篇介绍Ruby on Rails(RoR)的文章,忍不住自己也下载了一份来体验一下,简单说说感受。

参考文档建立了一个很简单的系统,没花多少时间,这主要归功于scaffold函数提供了缺省的web界面。要想修改这个界面却不那么简单,配置上只要修改一两处,但必须手写一个.rhtml模板文件,这里面存在不少代码,而且ruby代码和html代码交叉得很厉害,和asp差不太多。当然,我想这一步是使用任何框架都无法避免的,看到有文章说RoR的开发效率是struts的十倍,我保留意见。

RoR另一个提高开发效率的途径是做了很多假设来代替配置文件,例如控制文件都放在controller目录里,模型文件都放model目录,url映射就是控制文件名的前半部分,数据库表名与model的对应,等等。我很赞同这种方式,一是节约了写一堆xml配置文件的时间,二是任何熟悉RoR的人都能很快找到需要的类。

由于对Ruby并不熟悉,所以我看.rb文件里的代码会比较吃力。Ruby是解释型的语言,它在语法上有一些方便之处,例如变量的表示;而且它是比较彻底的面向对象语言,连数字123都是对象;三是省略了编译这个步骤,源代码修改后可以立即生效(Eclipse的增量编译基本上也可以达到这个效果);缺点应该是主要是性能方面,我想很可能比不上jsp。

长时间使用一种语言后,总想偶尔换换口味。Java是我最喜欢的无疑,同时也很羡慕掌握多门语言的高手,碰到问题先考虑用哪种语言实现,毕竟每门语言都有自己擅长。继续研究研究Ruby,也许它会成为我的另一杆枪。

另外,RDT是一个Ruby开发的Eclipse插件,但对RoR似乎没有特别的支持。除了.rb文件的编辑器外,它还专门集成了一个正则表达式验证工具,看来Ruby在这方面也比较在行。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/08/13/214329.html