This framework will be very easy understanding if you are familiar with Java Reflection. It just do four things:

  • Construct argument values of the method you written, by getting them from ObjectSource.
  • Invoke the method on the target object.
  • Save the method result into ObjectSource.
  • Process the URL dispatching.

ObjectSource is the core interface of this framework, for getting and saving objects. The most work you are doing is to tell this framework how to get object from ObjectSource, and save object into ObjectSource.

Executable is another core interface. It can be executed only with an ObjectSource.There are two implementations of this interface, one is Invoke, and another is Action. They both are advanced encapsulation of Java Method object, an Action can have one or more Invokes, and an Invoke are conresponding to a Method.

Resolver is an important concept, it is the target of Invoke's method. But there is no interface defined for it, because it is writing by you. You may call it "Business object".

Converter helps ObjectSource converting the object it stored to the Invoke's method argument type.

The detail structure of SoybeanMilk configuration file is listing bellow:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE soybean-milk PUBLIC "-//SoybeanMilk//DTD soybeanMilk web//EN" "http://soybeanmilk.googlecode.com/files/soybeanMilk-web-1.0.dtd">
  • <soybean-milk>                         Root element
    • <global-config>                   Global configuration block
    • <includes>                        Included modules
      • <file>                       Module file
    • <resolvers>                       Resolver configuration block
      • <resolver>                   Resolver
    • <executables>                     Executable configuration block
      • <invoke>                     Global Invoke
        • <arg>                   Arguments configuration
      • <action>                     Global Action
        • <invoke>                Local Invoke
          • <arg>              Arguments configuration
        • <ref>                   Reference to global Executable
        • <target>                Target

<global-config> (Optional)

Global configuration block. It is just defined as a parent tag with no attribute.

<generic-converter> (Optional)

Generic Converter configuration. Type conversion is a nest function of this framework, it works when you want to get an object which has different type with the object stored in ObjectSource (See GenericConverter interface).

Especially in web environment, the request values are all Strings, but the argument of Invoke method may be any type.

It contains the following attributes:

  • class (Optional)
    Customized generic converter class with which you can completely replace the nest generic converter. This customized generic converter must be an implementaion of GenericConverter interface.
    The nest generic converter WebGenericConverter will be used if you didn't set this attribute.

<converter> (Optional)

Support Converter object. You can add new support converters or replace old converters to enhance the generic converter.

The nested generic converter implementation WebGenericConverter has add most common support converters (see DefaultGenericConverter class), and it can also convert "java.util.Map<String, Object>" to JavaBean object, JavaBean array and JavaBean collection(List, Set).

It contains the following attributes:

  • src (Required)
    The source type it can convert from. Such as "java.lang.String" (String class), "[Ljava.lang.String;" (String[] class).
  • target (Required)
    The target type it can convert to. Such as "int" (int class), "[I" (int[] class)
  • class (Required)
    The class of this converter.

The framework defines some shortname for src and target attribute value, The rule is listing below:

  • "request" means javax.servlet.http.HttpServletRequest class
  • "session" means javax.servlet.http.HttpSession class
  • "application" means javax.servlet.ServletContext class
  • "response" means javax.servlet.http.HttpServletResponse class
  • Primitive type, their wrapper type, and String, BigDecimal, BigInteger, java.util.Date type, also with their array type, are the same with their Java syntax, and do not need its package prefix, such as :
    "int", "Integer", "int[]", "Integer[]", "String", "String[]", "Date[]", "BigDecimal[]".
  • The following types
    java.sql.Date
    java.sql.Time
    java.sql.Timestamp
    are familiar with above, but package prefix is required, such as :
    "java.sql.Time", "java.sql.Time[]".

The following is a support converter example, the nested will be replaced:

<generic-converter>
	<converter src="String" target="Date" class="your.converter.DateConverter" />
	<converter src="String[]" target="Date[]" class="your.converter.DateArrayConverter" />
	<converter src="request" target="java.io.File" class="your.converter.FileConverter" />
</generic-converter>

<interceptor> (Optional)

Interceptor configuration. You can add "before", "after", "exception" interceptor for Executor using this configuration.

It contains the following attributes:

  • before (Optional)
    Set the "before" interceptor's global Executable name. Executor will execute it first before the current Executable.
  • after (Optional)
    Set the "after" interceptor's global Executable name. Executor will execute it after correctly executing the current Executable.
  • exception (Optional)
    Set the "exception" interceptor's global Executable name. Executor will execute it if exception thrown when executing the current Executable. Usually, you only need to handle InvocationExecuteException and ConvertExecuteException in your exception interceptor.
  • execution-key (Optional)
    Saving keyword of the execution context object Execution. With this object, you can get the execution context information in your interceptor.

The following is an "exception" interceptor example, the framework will execute Action "/handleException.do" when exception occuring:

<global-config>		
		<interceptor exception="/handleException.do"
			execution-key="session.execution" />
</global-config>

<executables>
		<action name="/handleException.do">
			<invoke method="handleException" resolver="exceptionResolver">
				<arg>session.execution</arg>
			</invoke>
			
			<target url="/error.jsp" />
		</action>
		...
		...
</executables>

It's corresponding Java method must be:

public void handleException(Execution execution)
{
	...
}

<includes> (Optional)

Included modules configuration. It is just defined as a parent tag with no attribute.

<file> (Optional)

The file of module configuration. It can be a resource file in classpath, or file in "/WEB-INF" folder of your application.

Note that only <resolvers> and <executables> tags are valid in module configuration files.

It's an example below:

<includes>
	<file>your/config/module.1.xml</file>
	<file>your/config/module.2.xml</file>
	<file>/WEB-INF/config/module.3.xml</file>
	<file>/WEB-INF/config/module.4.xml</file>
	<file>/home/yourapp/config/module.5.xml</file>
</includes>

<resolvers> (Optional)

Resolver configuration block. It is just defined as a parent tag with no attribute.

<resolver> (Optional)

Resolver configuration. You may add many resolvers here and using later.

It contains the following attributes:

  • id (Required)
    The id of Resolver, and will be used when you defining Executables.
  • class (Required)
    The class of Resolver. It single instance will be created by the parser of this framework, and using by all referenced Executables.

This is an example below:

<resolvers>
	<resolver id="yourResolver0" class="your.resolver.MyResolver0" />
	<resolver id="yourResolver1" class="your.resolver.MyResolver0" />
	<resolver id="yourResolver2" class="your.resolver.MyResolver1" />
</resolvers>

<executables> (Optional)

Executable configuration block.

It contains the following attributes:

  • prefix (Optional)
    Set the prefix of all the Executables in this configuration file scope, it will be useful in module configuration files.

<invoke> (Optional)

Invoke configuration. It can be directly added under the <executables> tag (as global), or added under the <action> tag (as local).

It has tow configuration mode:

XML mode

It contains the following attributes:

  • name (Optional)
    Your must set this attribute if this Invoke is global, and it must be unique together with Action's name attribute. It is optional if this Invoke is local.
  • method (Required)
    Set the method name of this Invoke, it must be defined in resolver-class or resolver class. The configuration parser will use this attribute to find the Method object. If you set the resolver-class attribute, it's class will be first used for finding, or the resolver attribute's class will be used.
  • resolver-class (Alternatively)
    Set the Resolver class name of this Invoke. If the Invoke method is static, you can only set this attribute, because static method can be exeucted without instance.
  • resolver (Alternatively)
    Set the Resolver id of this Invoke defined in <resolvers> tag. All the ID defined in module files can be used here. If the Invoke method is not static, this attribute is required.
  • result-key (Optional)
    The saving keyword of this Invoke method result. The result will always be saved if you set this attribute, and "null" is saved if the method has no return value. Contrarily, nothing will be saved.
    Iegal result keywords are listing below:
    • yourResultKey
      The result will be saved in "request" scope with keyword "yourResultKey".
    • request.yourResultKey
      The same as above.
    • session.yourResultKey
      The result will be saved in "session" scope with keyword "yourResultKey".
    • application.yourResultKey
      The result will be saved in "application" scope with keyword "yourResultKey".
    In fact, the format of keywords here are determined by WebObjectSource class, because they will be directly passed to its set(...) method.

Java mode

It makes your configuration for Invoke just like writing Java codes. The syntax is listed below:

<invoke name="yourInvokeName">
	resultKey = resolver.method(arg, arg, ...)
</invoke>
  • resultKey
    The same meaning to the above attribute result-key of <invoke>.
  • resolver
    The same meaning to the above attribute resolver or resolver-class of <invoke>. The framework can recognise which attribute it is.
  • method
    The same meaning to the above attribute method of <invoke>.
  • arg
    The same meaning to the below tag <arg>.

These are some configuration example using Java mode:

<action name="/multiply.do">
	<invoke>
		request.multiplyResult = calculator.multiply( param.a, 100)
	</invoke>
	<invoke>
		your.util.Utils.printToConsole(request.multiplyResult);
	</invoke>
</action>

<invoke name="yourGlobalInvokeName">
	request.yourResultKey = yourResolver.yourMethod(
		param.arg1, true, 'a', '\u00EF', "hello\t hello", 100 )
</invoke>

<arg> (Unsure)

The argument configuration of an Invoke method. It may be needed only if you are using XML mode to configure your <invoke>.

It contains no attributes, but only content. There are two types of <arg> content, one is keyword, and another is value. The framework can recognize them.

The legal content is listing below:

  • Keyword
    • param
      The entire request parameter map. If the Invoke method argument is java.util.Map type, then this entire map will be directly passed to the method. And if the argument is other type, the framework will first convert it to the target type, then pass it to the method.
    • yourArgKey
      The parameters start with "yourArgKey" in request parameter map. The framework will simplely convert it to the Invoke method type and then pass it, if it is explicitly (only one value). Otherwise, the framework will first creat a new map who contains parameters only start with "yourArgKey", and it's key will also be truncated from "beanName.propertyName" to "propertyName". Then, it will directly pass this new map or pass the converted object mentioned as above.
    • param.yourArgKey
      The same as above.
    • request
      The HttpServletRequest object. By default, the framework can not convert it. So, you must define your own support converter who converting "javax.servlet.http.HttpServletRequest" source to your destination type if the Invoke method argument is not HttpServletRequest.
    • request.yourArgKey
      The object whose key is "yourArgKey" in request scope. The framework will try to convert it to the Invoke method argument if they are not match.
    • session
      The HttpSession object. By default, the framework can not convert it. So, you must define your own support converter who converting "javax.servlet.http.HttpSession" source to your destination type if the Invoke method argument is not HttpSession.
    • session.yourArgKey
      The object whose key is "yourArgKey" in session scope. The framework will try to convert it to the Invoke method argument if they are not match.
    • application
      The ServletContext object. The framework can not convert it by default. So, you must define your own support converter who converting "javax.servlet.ServletContext" source to your destination type if the Invoke method argument is not ServletContext.
    • application.yourArgKey
      The object whose key is "yourArgKey" in application scope. The framework will try to convert it to the Invoke method argument if they are not match.
    • response
      The HttpServletResponse object. The framework can not convert it by default. So, you must define your own support converter who converting "javax.servlet.http.HttpServletResponse" source to your destination type if the Invoke method argument is not HttpServletResponse.
    • objectSource
      The current WebObjectSource object, you can get all the servlet objects from it. The framework can not convert it by default. So, you must define your own support converter who converting "org.soybeanMilk.web.os.WebObjectSource" source to your destination type if the Invoke method argument is not WebObjectSource.
    In fact, the format of keywords here are determined by WebObjectSource class, because they will be directly passed to its get(...) method.
  • Value
    It can only be Java primitive type, String or null with the same syntax as Java language.
    • boolean
      Boolean value (true or false). The coresponding argument must be "boolean" or "Boolean" type.
    • byte
      Byte value. The coresponding argument must be "byte" or "Byte" type.
    • char
      Character. It can be normal character (such as 'a', 'b', 'c', '1', '5'), Java ESC character ('\n', '\r', '\t', '\'', '\\', '\"'), or unicode character ('\uxxxx'). The coresponding argument must be "char" or "Character" type.
    • double
      Double value. The coresponding argument must be "double" or "Double" type.
    • float
      Float value. The coresponding argument must be "float" or "Float" type.
    • int
      Int value. The coresponding argument must be "int" or "Integer" type.
    • long
      Long value. The coresponding argument must be "long" or "Long" type.
    • short
      Short value. The coresponding argument must be "short" or "Short" type.
    • String
      String value. It can contain characters defined in char segment above. The coresponding argument must be "String" type.
    • null
      Null. The coresponding argument can be any type except primitive.

The following are some examples:

<invoke name="yourGlobalInvoke" method="yourMethod" resolver="yourResolver" result-key="yourResultKey">
	<arg>arg1</arg>
	<arg>param.arg2</arg>
	<arg>request.arg3</arg>
	<arg>100</arg>
	<arg>2.35f</arg>
	<arg>'a'</arg>
	<arg>"hello\t hello"</arg>
	<arg>true</arg>
	<arg>null</arg>
</invoke>

<invoke name="/multiply.do" method="multiply" resolver="calculator" result-key="multiplyResult">
	<arg>a<arg/>
	<arg>param.b<arg/>
</invoke>

<invoke name="/doMultiply.do" method="doMultiply" resolver="calculator">
	<arg>request</arg>
	<arg>response<arg/>
</invoke>

<action> (Optional)

Action configuration. You can add many <invoke> and <ref> tags into it, and they will be executed orderly.

It contains the following attributes:

  • name (Required)
    Set the name of this Action, often the servlet path you want it to process.
    You can also define variables in it, for RESTful support. For example:
    <action name="/user/{request.userId}/edit">
    	<invoke> userResolver.edit(request.userId) </invoke>
    </action>
    
    It will be able to process requests such as "/user/jack/edit" or "/user/tom/edit", and "jack" and "tom" will be put into WebObjectSource with key "request.userId" by the framework.

It is an example below:

<action name="/yourAction.do">
	<invoke method="method0" resolver="yourResolver" result-key="result0" />
	<invoke method="method1" resolver="yourResolver">
		<arg>param.a<arg/>
		<arg>request.result0<arg/>
		<arg>true<arg/>
	</invoke>
	<ref name="gloablInvoke0" />
	<ref name="gloablInvoke1" />
	
	<target url="/action_result.jsp" />
</action>

<ref> (Optional)

Reference configuration. It helps you add one or more global Executable into this Action.

It contains the following attributes:

  • name (Required)
    Set the global name of Executable you want to reference.
    You must set the full name (with prefix) of the target Executable if it is not in this configuration file scope.

<target> (Optional)

The target configuration of this Action.

It contains the following attributes:

  • url (Optional)
    The target URL. It must be start with "/" if it is an URL in your application.
    You can also use the keywords the WebObjectSource supported here for dynamic URL (With "{" prefix and "}" suffix). For example:
    <action name="/user/{request.userId}/edit">
    	<invoke> userResolver.edit(request.userId) </invoke>
    	<target url="/user/{request.userId}/view" type="redirect" />
    </action>
    
    The framework will redirect to "/user/jack/view" for request "/user/jack/edit".
  • type (Optional)
    The target type. The framework can process only two values : "forward" or "redirect". And it will treat it as "forward" type if you did not set it.
    You can also extend DispatchServlet for customized type supporting, such as JSON supporting as following:
    <action name="/user/list">
    	<invoke> userList=userResolver.query() </invoke>
    	<target type="json" />
    </action>