差沙的密码 -- SSHWSFC's code
阅读本Blog请自备塑料袋一只
posts - 14,  comments - 59,  trackbacks - 0
前些天去参加了Google的开发日活动,由于之后直接就去郊区所以一直没有时间写blog来谈谈感想。首先是google的OpenSocial,进入大厅签到之后转角过去就是个大网站的展台,每个柱子的一面是一个网站,都是OpenSocial的合作网站,有聚友,天际,天涯等。有幸跟聚友的CTO谭晓生先生和天际网的CTO郭应寿先生聊了聊OpenSocial使用中的益处和遇到的问题,感觉OpenSocial目前还是不太成熟,对比FB来说还有一定的差距,比如没有照片处理等等问题。而且我始终有一种感觉,OpenSocial是一个google的标准,但是各大网站在使用OpenSocial的时候几乎都会加上自己的扩展,这样下去,最终OpenSocial统一平台的目标很难实现,因为两个网站的Gidget几乎不能通用,这些问题OpenSocial的合作网站跟Google也在讨论中。另外得到的消息是,OpenSocial可能也会考虑出一套XXml,规范化OpenSocial的使用,并借此加强安全性。
稍后的OpenSocial专题topic中了解到了更多有关OpenSocial的信息,主要是Shindig了, 前一天还在跟白衣提议用ss做一个类似Shindig的东西,主要是做一个OpenSocial的实现,当时就想这玩意估计有了,但是没想到是host到apache了,在apache的孵化器中大家可以自己找找。会议的时候逮住了一个google的人问了一堆问题,过后才知道原来是google中国sns的负责人。
最大的收获实在是太意外了,在中午休息的时候,走过过道到楼下吃饭,突然发现一个熟悉的身影坐在角落里一个人在玩电脑,我晕是Andy Rubin,Android的老大。居然就坐在那里,而且旁边的人没有人在意他,要知道听android topic的人可不少呀,居然每一个人认出他来的。我叫了朋友确认一下,准备上前打招呼。刚上去打了个招呼,握了个手,又来了一个人,他一下认出Andy,并且跟Andy简短的交谈了一下。我提议先照个相,照相后简短聊下那个人就走了。过后Andy说那个人就是Google中国负责android的老大邸烁博士,我狂晕呀两位老大都让我给碰上了。稍候跟Andy聊了一下Android的发展,随后他掏出一个android的原型机,就是google IO大会上的那种机器。这里要说明的是,不管在模拟器上android的表现如何,在真机上你的体验绝对绝对是不一样的,太COOL了。尤其是第二屏的widget效果,还有那个street view效果,真机拿在手上体验就是不一样呀~~~~ 随后又了解到了一些android目前的发展情况,这里可以告诉大家的是,android是绝对值得期待的~!!!

posted @ 2008-06-15 13:03 差沙 阅读(2782) | 评论 (4)编辑 收藏

经过两天的努力,终于在Android上面跑起来MSN了。现在实现了基本的聊天功能,打算陆续的把其他功能完善,万事开头难,相信以后的工作会很顺利。


Gtalk的功能由于有官方支持,早就弄了跟这个差不多的版本。


另外,这个软件目前是以插件的形式开发,多一个协议就是多一个apk,实现一些intent即可。官方的IM包里面有很多支持,可惜文档没有提及。

 

Android中文论坛原帖

posted @ 2008-03-21 01:59 差沙 阅读(4194) | 评论 (2)编辑 收藏

今天看到Air1.0和Ext2.0.2的发布消息,很是兴奋,要知道Ext的每个版本升级都是很实惠的。

Ext2.0.2的这次升级主要是针对Air的支持,看来他们的关系是越来越近了,我也借机小试了一下Air。

简单说说Air的开发过程:

1.先要下载jre 和 airSDK

2.jre就不说了,airSDK也是简单的解压一下便可。

可以看看SDK下面的bin目录,一共俩文件,一个adl一个adt,adl是用来测试的,adt使用编辑发布的(ADT怎么让我想起来android了)

3.创建一个普通的目录作为项目目录。OK,可以写页面了。就是写html js flash什么的,都可。

4.编写一个xml文件,名字不限。内容嘛~~~~

看看这个文件,airSDK\templates\descriptor-template.xml 里面有详细的解释,拿过来改改即可,给一个例子:


<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/1.0.M6">
    <id>examples.html.HelloWorld</id>
    <version>0.1</version>
    <filename>HelloWorld</filename>
    <initialWindow>
        <content>HelloWorld.html</content>
        <visible>true</visible>
        <width>400</width>
        <height>200</height>
    </initialWindow>
</application>


5.运行airSDK\bin\adl.exe (那个创建的xml文件名),运行即可。

PS:我一般用eclipse来编辑,用外部命令的工具来运行adl,很方便。

简单的说了一下Air的开发方法后,我们来简单的看一下Ext都对Air提供了什么支持。

1.Ext.air.FileProvider

Ext提供状态保存的功能,在对AIr的支持中Ext实现了一个Provider,这个Provider把状态保存在本地目录的一个文件里。使用方法如下:


// Initialize the state provider
Ext.state.Manager.setProvider(new Ext.air.FileProvider({
	file: 'ext.state',
	// if first time running
	defaultState : {
		mainWindow : {
			width:400,
			height:200,
			x:10,
			y:10
		}
	}
}));


制定Provider为Ext.air.FileProvider即可,这个操作一般在onReady里面执行。当然需要你先创建一个ext.state文本文件,在程序根目录下即可。这样ext的应用运行时,窗口等大小的调整,位置之类的调整都能保存下来了。很方便。

这里包装的Air代码主要是:

air.File.applicationStorageDirectory.resolvePath

air.FileStream

2.Ext.air.NativeWindow

Ext可以调用Air实现Native的窗口。使用方法如下:


	var win = new Ext.air.NativeWindow({
		id: 'mainWindow',
		instance: window.nativeWindow,
		minimizeToTray: true,
		trayIcon: 'ext-air/resources/icons/extlogo16.png',
		trayTip: 'Simple Tasks',
		trayMenu : [{
			text: 'Open Simple Tasks',
			handler: function(){
				win.activate();
			}
		}, '-', {
			text: 'Exit',
			handler: function(){
				air.NativeApplication.nativeApplication.exit();
			}
		}]
	});


我们可以看到,其中最吸引人的是系统托盘的功能,可以方便的指定系统托盘的各种属性。

这里包装的主要是:

window.runtime.flash.html.HTMLLoader.createRootWindow

air.Screen.mainScreen.visibleBounds

3.Ext.sql.Connection

这个是重头戏,对db的支持,我们知道Air的DB是采用sqlite3来实现的。Ext封装了Air的db操作,简化统一了一些,而且提供了Store的支持,非常强大:

让我们简单看一下用法:

首先取得connection

var conn = Ext.sql.Connection.getInstance();

而后打开db

conn.open('ext.db');//这里的文件如果不存在会自动创建

创建Table


        conn.createTable({
            name: 'user',
            key: 'userId',
            fields: [
			    {name: 'userId', type:'string'},
			    {name: 'name', type:'string'},
			    {name: 'pwd', type:'string'},
			    {name: 'email', type:'string'}
			]
    	});

取得一个Table

var userDao = conn.getTable('user','userId');

查询

var users = userDao.select();//这里可以写条件式

添加


var newuser = {
    userId : 'sshw',
    name : 'sshwsfc',
    pwd : 'test',
    email : 'sshwsfc@gmail.com'
} 

userDao.insert(newuser);


感觉使用上还是蛮不错的,大家可以一点点试试看。

值得一提的是,在源码中发现Connection这里设计成了工厂的模式,Connection是一个伪接口,实现除了Air的还有GoogleGear的,会根据所处环境选择,灰常的好。

功能还在研究中,有心得了一点点给大家分享,

PS:打算搞一个iphone的信息管理软件练练手。反正都是sqlite的

posted @ 2008-02-26 13:49 差沙 阅读(4789) | 评论 (4)编辑 收藏

在朋友和同事的极力推荐下最近开始看上了python,其实主要是还是因为python是2007年度语言,怎么的也要与时俱进呀.最近一路看来有些心得,希望能与大家分享,小弟其实也只接触不到一周的python,有说错的地方还望大家指出改正.

不打算从py的语法基础说起了,直接说说对django的心得:

接触django首先需要了解可能就是他那个model,建立一个model就什么都有了,这对于搞java得人员来说还是挺有吸引力的(当然貌似对于动态语言这都是小儿科),那么让我们先看一个model的例子:

偷懒了,直接拿django-admin里面的User出来了


class User(models.Model): 
    username = models.CharField(_('username'), maxlength=30, unique=True, validator_list=[validators.isAlphaNumeric])) 
    first_name = models.CharField(_('first name'), maxlength=30, blank=True) 
    last_name = models.CharField(_('last name'), maxlength=30, blank=True) 
    email = models.EmailField(_('e-mail address'), blank=True) 
    password = models.CharField(_('password'), maxlength=128)) 
    class Meta: 
        ordering = ('username',)

每个属性就是一个库表的字段,定义起来非常简单明了,models里面提供了很多种类的Field类似上面的EmailField。不同的Field有不同的设置,可以看相应的原来来了解相关的设置.

在model class内部还有一个class Meta,这个Class的属性制定了这个表的一些存取策略,例如这里的ordering。MetaClass里面的属性可以用model的_meta属性取得。OK,那么这样一个model怎么就能实现对数据库表的灵活操作了呢。让我们来看看吧。

首先先分析一下/django/django/db/models/base.py这个文件,其中包含了models.Model这类的定义:

看看class定义的第一行吧,第一行就够我琢磨一阵子的了:


class Model(object):
    __metaclass__ = ModelBase

Model采用了new style class定义,关于这个内容大家可以放狗看一下,第一行是一个__metaclass__属性的定义,该属性的值是ModelBase,这是一个类。__metaclass__的意思是,指定一个class,这个class的实例就是本class,相信您已经晕了。那么就拿这个Model的例子来说明一下,如果没有__metaclass__这个属性,产生一个实例就是正常的流程,有了这个属性流程会有改变:

首先调用BaseModel.__new__(cls, name, bases, attrs)这个方法,回返回的值是一个class类型,然后用这个class来创建实例。其实BaseModel就是Model的元类,来制定Model这个类的最终样子。关于元类的更多信息请看这里

那么我们的目光一下转移到BaseModel这个类上,我有种直觉,Meta这个class最后可以用_meta来取就是在这里做的手脚,看一下BaseModel的定义吧,有点长:


class ModelBase(type):
    "Metaclass for all models"
    def __new__(cls, name, bases, attrs):
        # If this isn't a subclass of Model, don't do anything special.
        if name == 'Model' or not filter(lambda b: issubclass(b, Model), bases):    #1
            return super(ModelBase, cls).__new__(cls, name, bases, attrs)

        # Create the class.
        new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')})    #2
        new_class.add_to_class('_meta', Options(attrs.pop('Meta', None)))     #3
        new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}))

        # Build complete list of parents                                      #4
        for base in bases:
            # TODO: Checking for the presence of '_meta' is hackish.
            if '_meta' in dir(base):
                new_class._meta.parents.append(base)
                new_class._meta.parents.extend(base._meta.parents)

        model_module = sys.modules[new_class.__module__]

        if getattr(new_class._meta, 'app_label', None) is None:
            # Figure out the app_label by looking one level up.
            # For 'django.contrib.sites.models', this would be 'sites'.
            new_class._meta.app_label = model_module.__name__.split('.')[-2]  #5

        # Bail out early if we have already created this class.
        m = get_model(new_class._meta.app_label, name, False)                 #6
        if m is not None:
            return m

        # Add all attributes to the class.
        for obj_name, obj in attrs.items():
            new_class.add_to_class(obj_name, obj)                             #7

        # Add Fields inherited from parents
        for parent in new_class._meta.parents:
            for field in parent._meta.fields:
                # Only add parent fields if they aren't defined for this class.
                try:
                    new_class._meta.get_field(field.name)
                except FieldDoesNotExist:
                    field.contribute_to_class(new_class, field.name)          #8

        new_class._prepare()

        register_models(new_class._meta.app_label, new_class)                 #9
        # Because of the way imports happen (recursively), we may or may not be
        # the first class for this model to register with the framework. There
        # should only be one class for each model, so we must always return the
        # registered version.
        return get_model(new_class._meta.app_label, name, False)              #10


简单分析一下这个代码:

1. 检查class是否为Model的子类,不是的话,不做任何处理,直接传给父类处理,也就相当于正常的处理了class,注意super在多重继承的时候应该严格使用

2. 用type来创建类,创建的就是正常的ModelClass

3. 这句很重要,add_to_class是Model里面的class方法,这个方法其实就是传入name和value,给Model添加class属性.看到了,原来神奇的_meta就是这么来的. 提到add_to_class方法,简单看一下它的代码:


    def add_to_class(cls, name, value):
        if name == 'Admin':
            assert type(value) == types.ClassType, "%r attribute of %s model must be a class, not a %s object" % (name, cls.__name__, type(value))
            value = AdminOptions(**dict([(k, v) for k, v in value.__dict__.items() if not k.startswith('_')]))
        if hasattr(value, 'contribute_to_class'):
            value.contribute_to_class(cls, name)
        else:
            setattr(cls, name, value)
    add_to_class = classmethod(add_to_class)


最后一句是制定这个方法是class方法,特点就是方法的第一个参数是本class,其实classmethod就是一个装饰器,在2。4之后可以使用@来简写。这里不得不提的是他对Admin的特殊处理,虽然AdminOption不是在admin模块里面的,但是这么做还是跟一个Admin的东东绑定起来了,在java的世界解耦是一件大事,看到下面还有对'contribute_to_class'这个方法的特殊处理,django为啥不弄的解耦点呢。而且同样是包装成Option,一个是在BaseModel里面弄(那个Meta的包装),一个在add_to_class方法里面弄,实在有点不优雅,可能还没了解太多,不知道他的深度用意吧。

4. Meta的集成,Option的这个类提供继承方法

5. 取得applabel,就是把model的名字分割取到数第二个,我很喜欢-2这样的设定

6. get_model方法取得缓存里面的东西。

7. 把所有的class attr拿出来搞一遍,一般的属性就setattr弄回去了,要是这个属性有contribute_to_class这个callable属性,那就执行之(Admin的处理完全也可以这样,其实我们常用的objects就是用这个方法弄的)

8. 每个Field调用自己的contribute_to_class方法来进行特殊的处理

9. 进入缓存,,暂且叫缓存吧,里面的东西大家看看很简单 文件在 /django/django/db/models/loading.py 里面还是有很多内容的

10.看注释说的很清楚了,我们一定要在缓存里面拿model。

 

其中需要指出的是,new_class._prepare() 这个方法,简单列出片段:


    def _prepare(cls):
        # Creates some methods once self._meta has been populated.
        opts = cls._meta
        opts._prepare(cls)

        ....

        dispatcher.send(signal=signals.class_prepared, sender=cls)

中间省略了一些代码,不是我没看懂的就是没意思的,关键要看这个dispatcher呀。这里是监听者模式,dispatcher.send(signal=signals.class_prepared, sender=cls)放松了一个包含特定信号的事件,让监听的人可以做相应的处理。这样的信号还有很多种,我们可以很简单的截获信号,处理相应的内容。也许您还记得,在我们创建db的时候,会提示创建一个超级用户,其实就是用这个方法来弄的。

那我为什么要把这个单独拿出来说呢,因为监听这个事件的人不是别人,是"objects"!!!在文件/django/django/db/models/manager.py的开头就有这样的代码:


def ensure_default_manager(sender):
    cls = sender
    if not hasattr(cls, '_default_manager'):
        # Create the default manager, if needed.
        try:
            cls._meta.get_field('objects')
            raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % cls.__name__
        except FieldDoesNotExist:
            pass
        cls.add_to_class('objects', Manager())

dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)


定义了一个callable,然后监听signals.class_prepared信号,呵呵,连上了吧,只要Class准备好了,就调用这个方法。简单判断有没有_default_manager'属性和meta里面的objects后,开始插入objects。cls.add_to_class('objects', Manager()) 很熟悉吧,前面讲过不多说了。

PS:写到这里不由得感叹,为啥同样是往Class里面加入东东,要搞这么多的花样呢,我以前也写过一个Rails配置加载过程的分析文档,虽然一个简单的加载配置就把所有动态语言玩个遍,但是这也太不规范了吧,可能不这么玩就不算“动态”语言了吧,哈哈。

 

终于Model的Class造好了,相信大家以后造自己的Class也能玩出更多的花样。那么可以开始下一步了。我到老家后再写吧,打算讲讲神奇的objects,也就是Manager,其实就是玩QuerySet。。那里有很多的__xxx__方法,很好很强大

posted @ 2008-02-02 17:32 差沙 阅读(3225) | 评论 (1)编辑 收藏
Android是google为手机开发的操作系统, 基于Linux2.6内核.


2007年11月12日(北美时间), Android的APP SDK公布, 开始了google手机的开发大赛. 我们致力于Android的开发文档翻译, 开发技术交流等工作, 希望为Android平台在中国的推广贡献自己的力量.


Andorid的SDK是完全针对java设计的, 甚至提供eclipse的插件, 作为一个javaer不研究实在可惜.

现在中文文档已经有了初步进展,  大家翻译的热情都很高涨. 论坛里的讨论也十分热烈, 欢迎您的访问.

我们的网站, http://www.androidcn.net

中文文档计划:

http://www.androidcn.net/wiki

PS: 管理员您好, 不知道这个符不符合首页发布的规则, 要是不妥我会撤下, 我们都想为Android中文化贡献力量
posted @ 2007-11-15 13:41 差沙 阅读(2323) | 评论 (0)编辑 收藏
     摘要: 相信大家看了Ext2.0后, 印象最深的应该是Ext的组件模式,很好的规范了组件设计,用Manager的统一管理,也是很好的方式.下面简单分析一下Ext的组件结构.  阅读全文
posted @ 2007-10-20 20:01 差沙 阅读(7706) | 评论 (25)编辑 收藏

ProcessInstance里面有一个findToken(String tokenPath)方法。这里面写的是tokenPath。 tokenPath跟文件系统的规则差不多。类似 /fork1/fork2 这样的。

那么,怎么到fork2下的一个分支token ?

token自己也有findToken这个方法,不同的是ProcessInstance的是以rootToken为起点,token自己的是以自己为起点。那么取到token2下面的分支

token2.findToken("token")
如果有返回token,没有返回null


至于token的名字,我刚才查源码才发现,只有在Fork节点才会给token赋予名字。。。

那么token2.findToken("token") 这里"token"那里来

命名的规则,如下。。 

    String tokenName = null;
    
if ( transitionName != null ) {
      
if ( ! parent.hasChild( transitionName ) ) {
        tokenName 
= transitionName;
      }
 else {
        
int i = 2;
        tokenName 
= transitionName + Integer.toString( i );
        
while ( parent.hasChild( tokenName ) ) {
          i
++;
          tokenName 
= transitionName + Integer.toString( i );
        }

      }

    }
 else // no transition name
      int size = ( parent.getChildren()!=null ? parent.getChildren().size()+1 : 1 );
      tokenName 
= Integer.toString(size);
    }

    
return tokenName; 



这里"token"那里来?这个token名字,你自己可以根据命名规则得到 。看到这里应该很明了了

token的命名规则;首先根据transitionName的名字命名,这个transitionName的名字是你自己的写的你不可能不知道。 如果transitionName的名字重名了,加自然数区分 当然,如果transitionName没有名字,用分支的个数+1作为名字。

应该比较清楚了。

PS:这个是qq聊天记录的整理版,所以说话比较诡异。为的是记录下来避免忘记,也感谢群里的朋友。发现互相解决问题是提高水平的一个捷径。。

posted @ 2007-04-24 16:04 差沙 阅读(2364) | 评论 (1)编辑 收藏

说ruby是怪异的语法有点不妥当,动态语言里面不乏这样的语法出现。但是看了一些源码后发现,使用ruby的用法真的各有不同,就像大家以前说的,ruby每个人写出来的可能都不同。

现来说Rails里面如何加载config的吧。

在java里面config绝对是一个resource文件,然后通过读取配置的工具加入进来,在分析处理。

在ROR里面完全不是这么回事。

1.首先大家要了解的是,在我们启动 ruby script/server 的时候,rails做了一系列的处理,最后他执行了environment.rb

ruby 代码
Rails::Initializer.run do  | config |     
  
#  这里能插入我们自己的配置。    
   #  config. 之类    
end   

这里的config其实是Initializer内部的一个变量,掌控全局的配置信息,我们可以使用这个config来配置我们想要的。Rails::Initializer.run的源码是这样的,yield再一次显示了他的威力,让我们在配置文件中得以配置config。然后实例化了一个initializer 之后,把config作为参数传入了。

ruby 代码
def  self.run(command  =  :process, configuration  =  Configuration.new)    
  
yield  configuration  if  block_given?    
  initializer 
=  new configuration    
  initializer.send(command)    
  initializer    
end   

我们接着往下走,可以看到initializer 做了一系列的初始化工作,包括load_path的设定,路由的初始化,和activerecord的初始化。我们关心的还是配置如何起作用,那么来看看environments目录下面的配置文件是如何导入的吧。

ruby 代码

def  load_environment    
  silence_warnings do   
    config 
=  configuration    
    constants 
=  self. class .constants    
    eval(IO.read(configuration.environment_path), binding)    
    (self.
class .constants  -  constants).each do  | const |     
      Object.const_set(const, self.
class .const_get(const))    
    end   
  end   
end   

IO.read(configuration.environment_path) ,,这里就不使用什么回调不回调了,而是干脆IO拿出来eval一把,这里也是吃了一惊,这样也可以呀~~~~~~~然后,我们可以看看,他处理常量的方法,把自己配置文件中的常量全部放入Object里面,起到全局常量的目的。

最绝的还是initialize_framework_settings,使用了有一个ruby的技巧。

ruby 代码

def  initialize_framework_settings    
  configuration.frameworks.each do 
| framework |     
    base_class 
=  framework.to_s.camelize.constantize.const_get( " Base " )    
   
    configuration.send(framework).each do 
| setting, value |     
      base_class.send(
" #{setting}= " , value)    
    end   
  end   
end   

configuration.frameworks里面存放的是rails个个组件的名字,比方说active_record之类。然后把这个名字大写转换,然后用constantize取得ActiveRecord这个Module(注意,这些东西都在activesupport里面呢,activesupport/lib/active_support/core_ext/string/inflections.rb )。然后用const_get取得这个模块的Base类,也就是ActiveRecord::Base这个类了(下面都叫做Base类),所有的Rails的组件都是这个命名规则改天我们自己想要做一个Rails的组件加进来,也可以这样(但是要稍微修改一个源码)。

然后,我们吧config里面的内容给Base类。configuration.send(framework)是调用一个组件名称的方法,比方说active_record,就是去的config里面的active_record属性(这是最基本的),通过后面的do我们可以看到config返回的是一个hash,然后把hash中每一个key作为变量,value为传入值,传入Base类。。。这里大家应该没什么问题了,看看我们的config文件是怎么写的吧。

ruby 代码
#  Settings specified here will take precedence over those in config/environment.rb    
   
#  In the development environment your application's code is reloaded on    
#
 every request.  This slows down response time but is perfect for development    
#
 since you don't have to restart the webserver when you make code changes.    
config.cache_classes  =  false   
   
#  Log error messages when you accidentally call methods on nil.    
config.whiny_nils  =  true   
   
#  Enable the breakpoint server that script/breakpointer connects to    
config.breakpoint_server  =  true   
   
#  Show full error reports and disable caching    
config.action_controller.consider_all_requests_local  =  true   
config.action_controller.perform_caching             
=  false   
config.action_view.cache_template_extensions         
=  false   
config.action_view.debug_rjs                         
=  true   
   
#  Don't care if the mailer can't send    
config.action_mailer.raise_delivery_errors  =  false   

哦,看着很晕吧,config就是我们的配置对象,按照我们上面的说法,config.action_view之类framework的变量应该是一个hash才对呀,如果是hash的话,不应该用这样的方式传入,可能会用 config.action_view = {:debug_rjs => true}来传入。

OK.我们来看这个变量到底是什么样的hash。

ruby 代码
def  initialize    
  .    
  .    
  
for  framework  in  default_frameworks    
    self.send(
" #{framework}= " , OrderedOptions.new)    
  end   
end   

在初始化这些变量的时候,Rails给他赋值为OrderedOptions.new。这个特殊的类型可能就是关键。

ruby 代码
class  OrderedOptions  <  OrderedHash  # :nodoc:    
   def  [] = (key, value)    
    super(key.to_sym, value)    
  end   
      
  
def  [](key)    
    super(key.to_sym)    
  end   
   
  
def  method_missing(name,  * args)    
    
if  name.to_s  =~   / (. * ) = $ /     
      self[$
1 .to_sym]  =  args.first    
    
else    
      self[name]    
    end   
  end   
end   

看到其中的玄妙了么,method_missing~~~!! 如果调用一个**=的方法 ,就像当用传入一个HASH的值,key就是方法的名字。

也就是:config.action_view.debug_rjs  = true 相当于config.action_view[:debug_rjs] = true

OK ,大体上描述了一下,可以看到简单的一个Rails初始化已经给我们展示了几乎全部ruby的靓丽之处,这能说明,这个亮点肯定是贯穿rails的基本,在以后的深入研究中我们就能看到了。

posted @ 2006-11-21 12:10 差沙 阅读(3737) | 评论 (2)编辑 收藏

为了记录自己看Rails源码的过程,全程记录无废话。

我们看看script/server都干了什么

require File.dirname(__FILE__)  +   ' /../config/boot '
require 
' commands/server '

引用了boot.rb这个文件。看来这个文件是rails启动的入口,来看看怎么回事吧。
unless defined?(RAILS_ROOT)
  root_path 
= File.join(File.dirname(__FILE__), '..')

  unless RUBY_PLATFORM 
=~ /mswin32/
    require 
'pathname'
    root_path 
= Pathname.new(root_path).cleanpath(true).to_s
  end

  RAILS_ROOT 
= root_path
end
这一部分定义了RAILS_ROOT这个系统的全局变量,指定了项目的根目录,大家可以在以后华丽的使用了。

下一部分是找到rails,粗略看一下。
  if File.directory?("#{RAILS_ROOT}/vendor/rails")
    require 
"#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
  
else
    require 
'rubygems'
这里能看到,他先跑到vendor/rails去找rails了,这就是我们为什么能在插件里用rails是原因。如果没有那么gems的干活。
接下来是初始化一下load_path,没什么看的了。boot.rb就这样的吧。

回到script/server的第二行,包含了'commands/server'这个文件,这个文件是什么?Rails的源码里面找吧。我们在Rails的源码里面找到这个文件。
require 'active_support'
require 
'fileutils'

begin
  require_library_or_gem 
'fcgi'
rescue Exception
  
# FCGI not available
end

server 
= case ARGV.first
  when 
"lighttpd"
    ARGV.shift
  when 
"webrick"
    ARGV.shift
  
else
    
if RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
      
"lighttpd"
    
else
      
"webrick"
    end
end

if server == "webrick"
  puts 
"=> Booting WEBrick"
else
  puts 
"=> Booting lighttpd (use 'script/server webrick' to force WEBrick)"
end

FileUtils.mkdir_p(
%w( tmp/sessions tmp/cache tmp/sockets ))
require 
"commands/servers/#{server}"
没想到ActiveRecord居然是在这里引用的,这个ActiveRecord里面扩展了很对Ruby的既有类型,所以我们看源码的时候如果发现有不熟悉的方法,就来这里找找,当然,看Rails的API是最好的选择。

从参数一目了然,我们可以传入server的名字,lighttpd和webrick,根据不同的server选择不同的server文件来读取。我们还是看看webrick的吧。

require 'webrick'
require 
'optparse'

OPTIONS 
= {
  :port            
=> 3000,
  :ip              
=> "0.0.0.0",
  :environment     
=> (ENV['RAILS_ENV'|| "development").dup,
  :server_root     
=> File.expand_path(RAILS_ROOT + "/public/"),
  :server_type     
=> WEBrick::SimpleServer,
  :charset         
=> "UTF-8",
  :mime_types      
=> WEBrick::HTTPUtils::DefaultMimeTypes
}

ARGV.options do 
|opts|
  script_name 
= File.basename($0)
  opts.banner 
= "Usage: ruby #{script_name} [options]"

  opts.separator 
""

  opts.on(
"-p""--port=port", Integer,
          
"Runs Rails on the specified port.",
          
"Default: 3000") { |v| OPTIONS[:port] = v }
  opts.on(
"-b""--binding=ip", String,
          
"Binds Rails to the specified ip.",
          
"Default: 0.0.0.0") { |v| OPTIONS[:ip] = v }
  opts.on(
"-e""--environment=name", String,
          
"Specifies the environment to run this server under (test/development/production).",
          
"Default: development") { |v| OPTIONS[:environment] = v }
  opts.on(
"-m""--mime-types=filename", String,
                  
"Specifies an Apache style mime.types configuration file to be used for mime types",
                  
"Default: none") { |mime_types_file| OPTIONS[:mime_types] = WEBrick::HTTPUtils::load_mime_types(mime_types_file) }

  opts.on(
"-d""--daemon",
          
"Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)."
          ) { OPTIONS[:server_type] 
= WEBrick::Daemon }

  opts.on(
"-c""--charset=charset", String,
          
"Set default charset for output.",
          
"Default: UTF-8") { |v| OPTIONS[:charset] = v }

  opts.separator 
""

  opts.on(
"-h""--help",
          
"Show this help message.") { puts opts; exit }

  opts.parse!
end

ENV[
"RAILS_ENV"= OPTIONS[:environment]
RAILS_ENV.replace(OPTIONS[:environment]) 
if defined?(RAILS_ENV)

require RAILS_ROOT 
+ "/config/environment"
require 
'webrick_server'

OPTIONS[
'working_directory'= File.expand_path(RAILS_ROOT)

puts 
"=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}"
puts 
"=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer
DispatchServlet.dispatch(OPTIONS)
本来不想把大段的代码贴上来,但是这里面的内容可能大家都比较关心,涉及到server的启动参数。
  :port            => 端口,
  :ip              
=> server ip,
  :environment     
=>运行环境,
  :server_root     
=> web访问的目录,(很多人问这个怎么改)
  :server_type     
=> WEBrick::SimpleServer,
  :charset         
=> "UTF-8", 编码
  :mime_types      
=> WEBrick::HTTPUtils::DefaultMimeTypes

后面的require 引入了两个文件,一个是'webrick_server',别看,就是他。另外一个是config/environment,这个文件是系统的全局配置文件,很重要,我们还是下次看看这个文件去吧。里面还真挺有意思。

(不知道blogjava让不让写ROR的文章,看到有人写了所以放在首页了,如果不妥,我会尽快删除)
posted @ 2006-11-20 23:43 差沙 阅读(4562) | 评论 (0)编辑 收藏
这两天找点时间看了看jbpm,其设计思想相当不错,功能强劲,而且几乎实现了工作流模型的所有要求。可扩展的思想也是贯穿了整个jbpm的设计思路,这都是能看得出来的优势。但是这也仅仅是限于表面的一两眼,如果真的研究起其代码来,你会惊讶的发现Jbpm的代码漏洞百出,其设计思想完全没有发挥出来,应该说,Jbpm的代码真是“秀逗”了。下面来举几个例子吧。另外,我还没有看svn上的最新代码,所以可能有的问题jbpm自己已经修复,那就谢天谢地了。下面的代码基于jbpm3.1

秀逗1。无中生有

看到最核心的JpdlXmlReader代码真实欲哭无泪,如果好好精简精简,至少能踢掉1/3的代码。而其中甚至有些无中生有的代码:
if ( (actorId!=null)
           
|| (pooledActors!=null
         ) 
{
        assignmentDelegation.setProcessDefinition(processDefinition);
        assignmentDelegation.setClassName(
"org.jbpm.taskmgmt.assignment.ActorAssignmentHandler");
        String configuration 
= "";
我们看到,如果符合之前的条件,就用代理类来代理org.jbpm.taskmgmt.assignment.ActorAssignmentHandler这个类,但是找了好半天也没有找到这个类。。。。 难道是让用户在自己的项目中用这个类么?还是为了兼容原来的程序,,就算是其中一种,但是,可但是,这个条件根本就不可能满足,也就是说这是段废话,而且还无中生有的出来个ActorAssignmentHandler。。

秀逗2。画蛇添足

应该是我的基本功不都扎实,实在是高不明白下面的代码在干什么。。。
  public void setActorId(String actorId) {
    DefaultAuthenticationService authenticationService 
= (DefaultAuthenticationService) services.getAuthenticationService();
    DefaultAuthenticationService defaultAuthenticationService 
= (DefaultAuthenticationService) authenticationService;
    defaultAuthenticationService.setActorId(actorId);
  }
先强制转换成DefaultAuthenticationService,然后再强制转换成DefaultAuthenticationService。。。。
而且这里这么设计基本上就把DefaultAuthenticationService实现的AuthenticationService接口晾在那里了,根本就是应该用AuthenticationService这个接口来说话才对。jbpm的service设计的扩展性很强,可自己配制。但如果这么用service的话,再怎么扩展也没用。

秀逗3。莫“名”其妙

Jbpm中变量的名字真的莫名其妙,很多明明是Map的类型他叫xxList,而不是Map的类型,他却叫xxMap。这个地方我相信应该是能体现出程序员编写程序的严谨性的地方,而Jbpm作的还不够好。

秀逗4。固若金汤

Jbpm的扩展性贯穿始终,但是在最重要的泳道的扩展上却小家子气起来。看看泳道类代理的扩展代码。
if (expression!=null){
        assignmentDelegation.setProcessDefinition(processDefinition);
        assignmentDelegation.setClassName(
"org.jbpm.identity.assignment.ExpressionAssignmentHandler");
        assignmentDelegation.setConfiguration(
"<expression>"+expression+"</expression>");
      
    }
写的很明确,如果泳道使用表达式来表示的那么就用代理类来代理处理表达式。。我本想,太好了,写我自己的表达式,然后代理交给Acegi来根据表达式分配ActorId,但是,可是,但可是。他的代理类居然是写死的,写得就是自己的java.identity包里面的东西,不是说java.identity设计的不好,但是一个综合系统的用户角色管理系统是不可能跟着你的jbpm走的。强行要加入的java.identity的设计有点保护自我的意思,真的固若金汤,让我结合acegi的想法又是难上加难。(不过还是有办法的。大家自己找找看)。

秀逗5。口径不一

口径不一就是指两个程序部分的结合不一致。这种例子很多,我举一个程序和xsd的冲突的例子。
Instantiator是jbpm代理里面一个比较不错的概念。代理功能之一是生成代理的类的实例,而Instantiator则是负责生成实例的机制,这个Instantiator设计的不错,可以在配制文件中的config-type属性来扩展。看程序。
      // find the instantiator
      instantiator = (Instantiator) instantiatorCache.get(configType);
      
if (instantiator == null{
        
// load the instantiator class
        Class instantiatorClass = classLoader.loadClass(configType);
        
// instantiate the instantiator with the default constructor
        instantiator = (Instantiator) instantiatorClass.newInstance();
        instantiatorCache.put(configType, instantiator);
      }
这里的设计很人性化,可以根据configType来用自己的构造器,但是xsd却不这么想。
      <xs:attribute name="config-type" default="field">
        
<xs:simpleType>
          
<xs:restriction base="xs:string">
            
<xs:enumeration value="field"/>
            
<xs:enumeration value="bean"/>
            
<xs:enumeration value="constructor"/>
            
<xs:enumeration value="configuration-property"/>
          
</xs:restriction>
        
</xs:simpleType>
      
</xs:attribute>
可以看到它限制了4种类型,别说使用自己的构造器了,就连他自己的XmlInstantiator都不再考虑范围之内,真是大义灭亲呀。。

构造器来这里的作用很大,我写了自己的spring构造器,构造的时候使用beanFactory来构造,这样就算是存在数据库里面的class也能当作spring的bean来处理。但是如果用xsd的话就会导致交验错误,所以索性把xsd去掉了,还好一切正常,就是感觉别扭点。

秀逗N。。。 能够看得出来Jbpm需要提高的地方还很多。但是这些问题应该是一些开发人员的小疏忽,相信在以后的版本中可以改进。不管再怎么秀逗,Jbpm在工作流中仍然保有着强劲的地位,对BPM模型的实现也作的最为全面。而jbpm的par热部署和IDE也是整个系统中的两大亮点,这些优点都是不可不提的,所以我仍旧支持Jbpm,希望他能更加迅速的发展壮大起来。。。。

PS:文中错误之处还望大家指出,我希望有些“秀逗”是我自己秀逗了。
posted @ 2006-08-24 11:07 差沙 阅读(5729) | 评论 (7)编辑 收藏
Acegi好早就实现了ACL(好像是0.5),但是使用起来确实有点麻烦,所以用的不是太广泛。这里简单的说明一下使用方法,希望有更多的朋友来试试。

首先要理解Acegi里面Voter的概念,ACL正是在一个Voter上扩展起来的。现来看一下AclVoter的配置。

    <bean id="aclBeanReadVoter" class="org.acegisecurity.vote.BasicAclEntryVoter">
        
<property name="processConfigAttribute">
            
<value>ACL_READ</value>
        
</property>
        
<property name="processDomainObjectClass">
            
<value>org.springside.modules.security.acl.domain.AclDomainAware</value>
        
</property>
        
<property name="aclManager">
            
<ref local="aclManager"/>
        
</property>
        
<property name="requirePermission">
            
<list>
                
<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
                
<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
            
</list>
        
</property>
    
</bean>
  1. ACL_READ指的是这个Voter对哪些SecurityConfig起作用,我们可以把ACL_READ配置在想要拦截的Method上。比方说我们要拦截readOrder这个方法,以实现ACL控制,可以这样配置。
    orderManager.readOrder=ACL_READ
  2. processDomainObjectClass指出哪些DomainObject是要进行ACL校验的。
  3. aclManager是一个比较重要的概念,主要负责在权限列表中根据用户和DomainObject取得acl列表。
  4. requirePermission指出要进行这个操作必须具备的acl权限,比方说read操作就必须有ADMINISTRATION或READ两个权限。

其实整个过程看下来比较清晰,下面来看一下AclManager如何配置。

    <!-- ========= ACCESS CONTROL LIST LOOKUP MANAGER DEFINITIONS ========= -->

    
<bean id="aclManager" class="org.acegisecurity.acl.AclProviderManager">
        
<property name="providers">
            
<list>
                
<ref local="basicAclProvider"/>
            
</list>
        
</property>
    
</bean>

    
<bean id="basicAclProvider" class="org.acegisecurity.acl.basic.BasicAclProvider">
        
<property name="basicAclDao">
            
<ref local="basicAclExtendedDao"/>
        
</property>
    
</bean>

    
<bean id="basicAclExtendedDao" class="org.acegisecurity.acl.basic.jdbc.JdbcExtendedDaoImpl">
        
<property name="dataSource">
            
<ref bean="dataSource"/>
        
</property>
    
</bean>

很明显ACLManager继承了Acegi的一贯风格,Provider可以提供多种取得ACL访问列表的途径,默认的是用basicAclProvider在数据库中取得。既然提到了数据库,那我们就来看一下Acegi默认提供的ACL在数据库里的保存表结构:

CREATE TABLE acl_object_identity (
id 
IDENTITY NOT NULL,
object_identity VARCHAR_IGNORECASE(
250NOT NULL,
parent_object 
INTEGER,
acl_class VARCHAR_IGNORECASE(
250NOT NULL,
CONSTRAINT unique_object_identity UNIQUE(object_identity),
FOREIGN KEY (parent_object) REFERENCES acl_object_identity(id)
);
CREATE TABLE acl_permission (
id 
IDENTITY NOT NULL,
acl_object_identity 
INTEGER NOT NULL,
recipient VARCHAR_IGNORECASE(
100NOT NULL,
mask 
INTEGER NOT NULL,
CONSTRAINT unique_recipient UNIQUE(acl_object_identity, recipient),
FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id)
);
  1. acl_object_identity表存放了所有受保护的domainObject的信息。其中object_identity字段保存了domainObject的class和id,默认的保存格式是:domainClass:domainObjectId。
  2. acl_permission 就是ACL权限列表了,recipient 是用户或角色信息,mask表示了这个用户或角色对这个domainObject的访问权限。注意这些信息的保存格式都是可以根据自己的需要改变的。

这样读取和删除的时候Acegi就能很好的完成拦截工作,但是读取一个List的时候,如何才能把该用户不能操作的domainObject剔除掉呢?这就需要afterInvocationManager来完成这个工作。下面来看下配置:

    <!-- ============== "AFTER INTERCEPTION" AUTHORIZATION DEFINITIONS =========== -->

    
<bean id="afterInvocationManager" class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">
        
<property name="providers">
            
<list>
                
<ref local="afterAclCollectionRead"/>
            
</list>
        
</property>
    
</bean>
    
<!-- Processes AFTER_ACL_COLLECTION_READ configuration settings -->
    
<bean id="afterAclCollectionRead" class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
        
<property name="aclManager">
            
<ref local="aclManager"/>
        
</property>
        
<property name="requirePermission">
            
<list>
                
<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
                
<ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ"/>
            
</list>
        
</property>
    
</bean>

afterAclCollectionRead会在拦截的方法执行结束的时候执行。主要的作用就是在返回的List中挨个检查domainObject的操作权限,然后根据requirePermission来剔除不符合的domainObject。
posted @ 2006-06-17 00:20 差沙 阅读(2267) | 评论 (4)编辑 收藏
这家伙很懒,但起码还是写了一句话。

<2006年6月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

常用链接

留言簿(8)

随笔分类

随笔档案

文章分类

搜索

  •  

最新评论

阅读排行榜

评论排行榜