Sunday, 1 May 2011

A little leap: invoking a HTML5 WebSocket from PL/SQL with Oracle Advanced Queuing (Oracle JMS).

How it started

Recently I had to organize a HTML5 coding Dojo within the company I work, the difficult part concerning this dojo was not HTML5 but finding an interesting use-case to code around. A major aspect to consider, concerning finding a feasible use-case, was the fact that the dojo audience was not a homogeneous group; on the contrary it was primarily formed by a mix of Java and PL/SQL programmers.

The use-case

To find a suitable use-case, I first launched a little intern poll to collect all ideas, the response was quite poor and none of the proposals met my needs, but just as I was considering to give up, l had a little epiphany :-)… What if I could invoke a WebSocket from PL/SQL code? Wouldn’t that be a nice use-case for the entire audience? This is what I came up with…

  • WebSocket gateway: Kaazing Gateway.
  • Oracle Advanced Queuing (Oracle JMS).
  • A self-written proxy between the WebSocket gateway and Oracle AQ.

Kaazing Gateway

First the easy part: install and configure the Kaazing Gateway.
I just downloaded Kaazing WebSocket Gateway - HTML5 Edition 3.1.6 here, and followed the getting started documentation.
For my use-case I added a new Service in the gateway-config.xml (for more details see Administrator’s Guide).

  1: <service>
  2:   	<accept>ws://hostname:8002/oaq</accept>
  3:   	<type>broadcast</type>
  4: 	<properties>
  5:      	 	<accept>udp://hostname:9876</accept>
  6:     	</properties>
  7: 
  8: 	<cross-site-constraint>
  9:     		<allow-origin>*</allow-origin>
 10:   	</cross-site-constraint>
 11:   </service>  
 12: 

Note
You have to adjust the hostname to your servers hostname.


Oracle Advanced Queuing


This section describes how to set up the Oracle Advanced Queuing.

  1: --Oracle schema grants needed for AQ
  2: 
  3: GRANT EXECUTE ON dbms_aq TO [user];
  4: GRANT EXECUTE ON dbms_aqadm TO [user];
  5: GRANT EXECUTE ON dbms_aqin to [user];
  6: 
  7: --Prepare schema for Oracle AQ
  8: 
  9: --Create object type
 10: 
 11: CREATE OR REPLACE TYPE notification_type AS OBJECT
 12: ( table_name        varchar2(100)
 13: , key_name          varchar2(100)
 14: , key_              number(10)
 15: , message           varchar2(1000)
 16: , user_             varchar2(20)
 17: , action	    varchar2(100)
 18: );
 19: 
 20: --Create queue table
 21: 
 22: BEGIN
 23:   dbms_aqadm.create_queue_table
 24:   ( queue_table         => 'aq_notifications'
 25:   , queue_payload_type  => 'notification_type'
 26:   , multiple_consumers  => false
 27:   , comment             => 'AQ Notification Queue'
 28:   );
 29: END;
 30: 
 31: --Create queue
 32: 
 33: BEGIN
 34:   dbms_aqadm.create_queue
 35:   ( queue_name  => 'notification_queue'
 36:   , queue_table => 'aq_notifications'
 37:   );
 38: END;
 39: 
 40: --Start queuing
 41: 
 42: BEGIN
 43:   dbms_aqadm.start_queue( queue_name => 'notification_queue');
 44: END;
 45: 
 46: --Procedure to enqueue new notifcations
 47: 
 48: CREATE OR REPLACE
 49: PROCEDURE enqueue_notification_type
 50: ( p_notification in notification_type)
 51: IS
 52:   queue_options       dbms_aq.enqueue_options_t;
 53:   message_properties  dbms_aq.message_properties_t;
 54:   message_id          raw(16);
 55: BEGIN
 56:   dbms_aq.enqueue
 57:   ( queue_name         => 'notification_queue'
 58:   , enqueue_options    => queue_options
 59:   , message_properties => message_properties
 60:   , payload            => p_notification
 61:   , msgid              => message_id
 62:   );
 63: END;
 64: 
 65: --Enqueue a Message
 66: 
 67: EXEC enqueue_notification_type( p_notification  => notification_type('DummyAQ', 'ID', 1, 'Test 1', 'SYS', 'EXEC'));
 68: COMMIT;

Important note
The Java proxy described in the next section needs a Java Wrapper Type for the Oracle notification object type.
If you change the notification Oracle object type, the proxy has to be rebuilt with a new Java notification type wrapper object.
The java notification type wrapper object is generated via JPublisher (which comes with SQLJ). To generate the wrapper you have to execute the following command:
jpub -user=[user]/[password] -sql=Notification_Type -usertypes=oracle -methods=false


Oracle AQ WebSocket Proxy



The proxy consists of three classes:


  • AQWebSocketProxy: the Proxy.
  • Notification_Type: Java Wrapper for the Oracle Notification object type.
  • Notification_TypeRef: Java Wrapper for the Oracle Notification object type.

The next snippets are code snippets from the proxy class.
The most import things the class does are:
Listening to the Oracle AQ and dequeue a message as soon as it comes available on the queue.

  1:     private void dequeue() {
  2:         try {
  3:             LOGGER.info("Start listening single consumer queue...");
  4:             final AQOracleDriver driver = new AQOracleDriver();
  5:             while (true) {
  6:                 final AQSession session = driver.createAQSession(connection);
  7:                 //Create reference to qeue.
  8:                 final AQQueue queue = session.getQueue(username, queueName);
  9:                 final AQDequeueOption dequeueOption = new AQDequeueOption();
 10:                 //Set dequeue mode to BROWSE (messages won't get deleted after dequeue).
 11:                 //dequeueOption.setDequeueMode(AQDequeueOption.DEQUEUE_BROWSE);
 12:                 dequeueOption.setWaitTime(AQDequeueOption.WAIT_FOREVER);
 13:                 LOGGER.info("Waiting for message to dequeue...");
 14:                 final AQMessage message = queue.dequeue(dequeueOption, Notification_Type.getORADataFactory());
 15:                 final byte[] msgId = message.getMessageId();
 16:                 LOGGER.info("Dequeue starts for message: " + msgId + "  ...");
 17:                 final AQObjectPayload payload = message.getObjectPayload();
 18:                 //Retrieve data.
 19:                 final Notification_Type messageData = (Notification_Type) payload.getPayloadData();
 20:                 LOGGER.debug("[BEGIN Message Data]");
 21:                 LOGGER.debug(messageData.getTableName());
 22:                 LOGGER.debug(messageData.getKeyName());
 23:                 LOGGER.debug(messageData.getKey());
 24:                 LOGGER.debug(messageData.getMessage());
 25:                 LOGGER.debug(messageData.getUser());
 26:                 LOGGER.debug(messageData.getAction());
 27:                 LOGGER.debug("[END Message Data]");
 28:                 connection.commit();
 29:                 sendUDPPacket(messageData);
 30:                 LOGGER.info("Dequeue for message: " + msgId + " done.");
 31:             }
 32:         } catch (SQLException sqlex) {
 33:             LOGGER.error(sqlex.getMessage(), sqlex);
 34:         } catch (AQException aqe) {
 35:             LOGGER.error(aqe.getMessage(), aqe);
 36:         } catch (SocketException se) {
 37:             LOGGER.error(se.getMessage(), se);
 38:         } catch (UnknownHostException uhe) {
 39:             LOGGER.error(uhe.getMessage(), uhe);
 40:         } catch (IOException ioe) {
 41:             LOGGER.error(ioe.getMessage(), ioe);
 42:         }
 43:     }

The dequeued message will be translated to JSON first and then it will be send to the WebSocket gateway via the UDP protocol.

  1:     private String convertToJSON(final Notification_Type message) {
  2:         final JsonFactory factory = new JsonFactory();
  3:         final StringWriter writer = new StringWriter();
  4:         JsonGenerator gen;
  5:         try {
  6:             gen = factory.createJsonGenerator(writer);
  7:             gen.writeStartObject();
  8:             gen.writeStringField("TableName", message.getTableName());
  9:             gen.writeStringField("KeyName", message.getKeyName());
 10:             gen.writeNumberField("Key", message.getKey());
 11:             gen.writeStringField("Message", message.getMessage());
 12:             gen.writeStringField("User", message.getUser());
 13:             gen.writeStringField("Action", message.getAction());
 14:             gen.writeEndObject();
 15:             gen.close();
 16:         } catch (IOException ioe) {
 17:             LOGGER.error(ioe.getMessage(), ioe);
 18:         } catch (SQLException se) {
 19:             LOGGER.error(se.getMessage(), se);
 20:         }
 21:         final String json = writer.getBuffer().toString();
 22:         LOGGER.debug("[BEGING JSON Format]");
 23:         LOGGER.debug(json);
 24:         LOGGER.debug("[END JSON Format]");
 25:         return json;
 26:     }
  1:     protected void sendUDPPacket(final Notification_Type message) throws SocketException, UnknownHostException,
  2:                                                                          IOException {
  3:         final DatagramSocket clientSocket = new DatagramSocket();
  4:         final InetAddress IPAddress = InetAddress.getByName(gatewayHost);
  5:         byte[] sendData = new byte[1024];
  6:         final String jsonMessage = convertToJSON(message);
  7:         sendData = jsonMessage.getBytes();
  8:         final DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 9876);
  9:         clientSocket.send(sendPacket);
 10:     }

I made one little adjustment to the Wrapper files to make them ‘schema’ independent (I pass the schema name from the Proxy class):

  1: public class Notification_TypeRef implements ORAData, ORADataFactory {
  2:     public static final String _SQL_BASETYPE = AQWebSocketProxy.username + ".NOTIFICATION_TYPE";
  1: public class Notification_Type implements ORAData, ORADataFactory {
  2:     public static final String _SQL_NAME = AQWebSocketProxy.username + ".NOTIFICATION_TYPE";

The complete source code of the proxy is available in the download section below. It includes a runnable jar, all the source code, a HTML test page and a complete README file to get you started right away.


Run the Proxy and test it


First run the Kaazing Gateway.
Put the HTML5 WebSocket test page (which is included in the zip file –don’t forget to adjust the hostname to point to the Kaazing gateway host) on a webserver and open it in Google Chrome.
Then Run the AQWebSocketProxy (I ran it on the same server as the Kaazing gateway):
java -jar AQWebSocketProxy.jar -user scott -pwd tiger -dbhost localhost -dbsid ORCL -queue notification_queue -wsgateway localhost –debug

If everything works fine you can now execute PL/SQL code and see the result in your browser.

  1: EXEC enqueue_notification_type( p_notification  => notification_type('DummyAQ', 'ID', 1, 'Test 1', 'SYS', 'EXEC'));
  2: COMMIT;

Download & Documentation


The complete source code of the proxy is available here, it includes a runnable jar, all the source code, a HTML test page and a complete README file to get you started in seconds.


References


Oracle Advanced Queuing by Example
Advanced Queuing -- Java API
Oracle Streams AQ JMS Interface: Basic Operations
Oracle Advanced Queuing (1)
Oracle Advanced Queuing (2)
JPublisher with the 11g database
DBMS_AQADM
Oracle Streams AQ Operational Interface: Basic Operations
oracle aq and java jms
Oracle Advanced Queuing and JMS

Wednesday, 14 July 2010

Implementing CMIS

Since the first of may the first version of the CMIS spec is finalized. This new standard is ideally suited for Repository-to-Repository (R2R) and Application-to-Repository (A2R) CMS integration. In this post I’ll try to give you a brief overview of the possibilities and focus points concerning a CMIS integration.

The specification

CMIS allows you to communicate with a CMIS compliant CMS via a standard way. The communication can be based on Webs Services or on Restful AtomPub. If you can freely choose between these two technologies I definitely would go for AtomPub because it’s the most complete one (the spec handles AtomPub in almost 100 pages, Web Services takes only 3 pages).

So does a new standard for integrating a CMS solves all your problems/needs? The answer -of course- is No. After reading the spec this is my major concern:

2.1.1.1 Optional Capabilities “...Thus, a repository implementation may not necessarily be able to support all CMIS capabilities. A few CMIS capabilities are therefore “optional” for a repository to be compliant…”

It states clearly that a compliant CMS not necessarily will support the whole spec. Especially if you have the idea to write an implementation which can handle multiple Content Management Systems at once, this could be a pitfall.
Luckily CMIS has a query service available to retrieve the possibilities of a repository, and it is clear, you should first investigate the repository's possibilities (to figure out how much of the spec is implemented) before integrating a CMS via CMIS. You find the information about this in the spec in the following chapter:

2.2.2.2 getRepositoryInfo
Description: Returns information about the CMIS repository, the optional capabilities it supports and its Access Control information if applicable.

Apache Chemistry

Before going into detail on the architecture I would like to introduce another framework to you: Apache Chemistry. if you are a Java, Phyton, PHP or JavaScript developer you have the option to enable your application for CMIS by adding an extra layer based on this framework.
Apache Chemistry provides open source implementations of the Content Management Interoperability Services (CMIS) specification. There is an API available for different languages, but since I’m a Java guy, I will only focus on the Java API.

The Apache Chemistry Java API is named OpenCMIS.

OpenCMIS

OpenCMIS is a collection of Java libraries, frameworks and tools around the CMIS specification and is very complete. The main thing that is lacking for the moment (of course) is documentation. There are three main parts in the OpenCMIS collection: CMIS Client (consisting of Client API and Client Bindings API), OpenCMIS Server Framework and CMIS Browser. In this blog post I will only focus on the CMIS Client.

OpenCMIS Client

As said before the OpenCMIS Client contains two separate API’s: the Client API and the Client Bindings API.
The OpenCMIS Client Bindings API hides the CMIS AtomPub and Web Services bindings and provides an interface that is very similar to the CMIS domain model. The services, operations, parameters, and structures are named after the CMIS domain model and behave as described in the CMIS specification.

The primary objective of the Client Bindings API is to be complete, covering all CMIS operations and extension points. The result is a somewhat clunky interface. The Client API sits on top of the Binding API and exposes a nicer and simpler to use interface. It is the better choice for most applications.

Architectures

A2R (Application-to-Repository)

arch3

A2R (with OpenCMIS)

arch4

R2R (Repository-to-Repository)

arch5

Put it all in practice

Since there’s almost no documentation available at this point I’ll share some code with you. It’s not too hard to get something working but most of the time to get there you have to dive in the source code of OpenCMIS from time to time.
In contrast to the OpenCMIS documentation the CMIS spec however is very complete, and there are a lot of useful examples based on AtomPub.

Another big help is the Alfresco Website, there even is a test site which you can use to test you implementation.

The code
Java imports:
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.QueryResult;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.api.SessionFactory;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.enums.BindingType;




Connecting to a repository:

SessionFactory f = SessionFactoryImpl.newInstance();
Map<String, String> parameter = new HashMap<String, String>();

//user credentials
parameter.put(SessionParameter.USER, "admin");
parameter.put(SessionParameter.PASSWORD, "admin");
parameter.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
parameter.put(SessionParameter.ATOMPUB_URL,
"http://a.url.be:8888/alfresco/service/cmis");
parameter.put(SessionParameter.REPOSITORY_ID,
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx");

//session locale
parameter.put(SessionParameter.LOCALE_ISO3166_COUNTRY, "BE");
parameter.put(SessionParameter.LOCALE_ISO639_LANGUAGE, "nl");

// create session
Session s = f.createSession(parameter);




Retrieve a folder:

//Retrieve a Folder
ObjectId objectID =
s.createObjectId("workspace://SpacesStore/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
CmisObject CO = s.getObject(objectID);
System.out.println(CO.getName());
System.out.println(CO.getType().getDescription());




Query the repository:

ItemIterable<QueryResult> q =
s.query("SELECT * FROM cmis:document WHERE CONTAINS('RS232') AND " +
"cmis:createdBy = 'admin' AND IN_FOLDER(" +
"'workspace://SpacesStore/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')", true);

int i = 0;
for (QueryResult qr : q) {
System.out.println("-->");
System.out.println(qr.getPropertyById("cmis:objectTypeId").getFirstValue());
System.out.println(qr.getPropertyById("cmis:name").getFirstValue());
System.out.println(qr.getPropertyById("cmis:createdBy").getFirstValue());
System.out.println(qr.getPropertyById("cmis:objectId").getFirstValue());
System.out.println(qr.getPropertyById("cmis:contentStreamFileName")
.getFirstValue());
System.out.println(qr.getPropertyById("cmis:contentStreamMimeType")
.getFirstValue());
System.out.println(qr.getPropertyById("cmis:contentStreamLength")
.getFirstValue());
System.out.println(qr.getPropertyById("cmis:contentStreamId").getFirstValue());
System.out.println("--------------------------------------------------------");
}




Insert a new document:

//Insert new document
try {
File file = new File("C:\\afile.pdf");
Map<String, String> properties = new HashMap<String, String>();
properties.put(PropertyIds.NAME, file.getName());
properties.put(PropertyIds.CHECKIN_COMMENT, "");
properties.put(PropertyIds.OBJECT_TYPE_ID, ObjectType.DOCUMENT_BASETYPE_ID);
ContentStream contentStream = new ContentStreamImpl(file.getName(),
BigInteger.valueOf(file.length()),
new MimetypesFileTypeMap().getContentType(file), new FileInputStream(file));
session.createDocument(properties,
session.createObjectId("workspace://SpacesStore/xxxxx-xxxx-xxxx-xxxx-xxxxxxx"),
contentStream, VersioningState.MAJOR, new ArrayList(), new ArrayList(),
new ArrayList());
} catch (Exception e) {
e.printStackTrace();
}




That’s all for the moment folks.

References

CMIS Specification: http://docs.oasis-open.org/cmis/CMIS/v1.0/cmis-spec-v1.0.pdf
Apache Chemistry: https://cwiki.apache.org/CMIS/index.html
Alfresco: http://cmis.alfresco.com/, http://wiki.alfresco.com/wiki/CMIS

Tuesday, 22 June 2010

jBPM Custom Mail Producer

As stated in the jBPM developers guide (13.4.1. Custom Producers) it is possible to plug in your own custom mail producer.

However, the topic in the developers guide is very brief and just by implementing the interface or extending the MailProducerImpl your code won’t work.

Option one: “Google”…. No luck this time - makes me wondering how many people really use jBPM 4?

Second option: diving into the source code myself and complete the puzzle on my own. After a couple of hours I came to a solutions that does the trick.

As you can see in the source code instantiating a mail provider is done in org.jbpm.jpdl.internal.xml.JpdlParser:

C1

So to enable a process to use a custom mail producer you extend the notification node with a class attribute as shown below:

J1

So far, so good.
Final step: writing your custom producer.
I started simply by creating an empty class which extends from MailProducerImpl :

C2

Running my code however resulted in the following error: Exception in thread "main" java.lang.NullPointerException
at org.jbpm.pvm.internal.email.impl.MailProducerImpl.fillFrom(MailProducerImpl.java:97)…

C3

It learned me that in case of specifying a custom mail producer a mail template is not set.
org.jbpm.jpdl.internal.xml.JpdlParser:

C4

To solve this error adjust the constructor of the mail producer by adding the following code:

C5

At the end this is how my Mail producer looks like (it is still extended from the default mail producer but at this point just implementing the interface would probably do the trick)

Last thing I want to mention is the override of the produce method. In that method I retrieve the current task from the TaskContext. This opens the possibility to use it in the other methods of the class . (Below also a code snippet from the org.jbpm.jpdl.internal.activity.MailListener which is responsible for maintaining the TaskContext).

C6

C7

Now you can spam yourself while testing and fine tuning your code :-)

Wednesday, 9 June 2010

5 things you should know when developing products with ADF11g

I’m trying to get this talk into OOW via Oracle Mix. So If you want to see my talk: 5 things you should know when developing products with ADF11g you should take a minute or 2 and vote for it.

https://mix.oracle.com/oow10/proposals

Thursday, 4 March 2010

Java Memory Management

When writing programs it’s always good to know what’s happing in the core of the Framework you’re using. This allow you to get a better idea why things happen in a certain way or what to do in case you receive an error.

So if you’re a java programmer a good thing to read is this paper on memory management in the Virtual machine. It has a short introduction, a brief overview of the concepts and later on it dives a little deeper with an overview on the different garbage collectors and a chapter on ergonomics. At the end you get some tips and recommendations.

Chapter 6 also contains the paragraph ‘What to Do about OutOfMemoryError’ helpful for us all I think :-) (especially when you’ve ever have used BufferedImage)

Here you can find more information on Java HotSpot Garbage Collection.

Monday, 22 February 2010

ADF automated deployment: Hudson and WLST

As I told in my previous post on Hudson I had the wish/idea to extend my Hudson build script in a way that it would fully automate the deployment of the ear file to a WebLogic server.

So I gave it a try…

With the help of a blog post of Jay senSharma it actually was quite an easy job.

Step 1:
I copied the MIDDLEWARE_HOME\wlserver_10.3\server\bin\setWLSEnv.cmd to C:\Hudson_Slave_Node\workspace, and renamed it to deployWLS.cmd

Step 2
I created a python script file called deployDummy.py and added it to the same directory.

This is the content of the python file
connect('weblogic','weblogic1','t3://localhost:7101')
edit()
startEdit()
deploy('dummy_application1','C:\Hudson_Slave_Node\workspace\trunk\dummy.ear',targets='DefaultServer')
save()
activate()
startApplication(' dummy_application1')




Step 3:
I changed the bottom of deployWLS.cmd to look like:

@echo Your environment has been set.

@echo Deploy to WLS
java weblogic.WLST C:\Hudson_Slave_Node\workspace\deployDummy.py
@echo Deploy to WLS Finished

:finish

Step 4:
I added a new ‘Windows batch command’ Build step in Hudson that executes the deployWLS.cmd file.



Step 5:
Grab a pint of (Belgium) Beer, sit down, relax and see how the job is now done for you... :-)

Troubleshooting

If you receive the following error:

weblogic.application.ModuleException:
at weblogic.jdbc.module.JDBCModule.prepare(JDBCModule.java:290)
at weblogic.application.internal.flow.ModuleListenerInvoker.prepare ModuleListenerInvoker.java:93)
...
at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
Caused by: weblogic.common.ResourceException: java.security.PrivilegedActionException: weblogic.common.ResourceException: java.security.PrivilegedActionException: weblogic.common.ResourceException: No credential mapper entry found for password indirection user=cm for data source CM
at weblogic.jdbc.common.internal.DataSourceConnectionPoolConfig.getPoolProperties(DataSourceConnectionPoolConfig.java:84)
...
at weblogic.jdbc.module.JDBCModule.prepare(JDBCModule.java:252)

You have to adjust your application properties in a way that they‘ll not include the weblogic-jdbc.xml. It’s not possible to automatically deploy your ear file with this option. So uncheck this option and add a DataSource with the correct JNDI name via the WLS console.




References
Jaysensharma's Blog
Forum: WebLogic Server - Upgrade / Install / Environment / Migration
Thread: WLST to deploy ear file
Using the WebLogic Scripting Tool
WLST Command and Variable Reference

Sunday, 14 February 2010

ADF Methodology: Customization

I started a new thread in the ADF Enterprise Methodology Group about customization.

I’ll share my question also here on my blog. If you would like to comment on this subject I suggest you do it in the thread at the ADF Enterprise Methodology Group.

This is the question...

Hi All,
We’re currently developing a product with ADF 11g technology (Rich Faces/BC).We already finished our first version and are busy with our first customer’s implementations. Since it’s a product it’s the idea to have the same basic product (core) installed for all the customers (for a specific version) and to do individual customizations (if necessary) for each one of them.

For the customizations there are two important requirements:
-The implementation has to be maintainable (we don’t’ want to do bugfixing for each individual implementation but only in our shared product core). So the implementations of the customizations have to guarantee not to pollute the product core.
-Secondly our product has to remain upgradeable. If our product core evolves to a new version it has to be possible to do an upgrade without rewriting the customizations.

To achieve this we already used the following strategy:
-The use of flexible fields and flexible tab pages (which doesn’t require any coding)
-The use of a Business rules engine
-The use of a flexible BPM engine

This makes it possible to reduce the amount of necessary customizations to a minimum but as always extra customization is sometimes needed. (For example the integration with another system, an extra application, ... )For these we still have two ideas/options:
-Modularization (this subject probably needs a thread for its own)
-MDS: first consideration here is that every site customization is implemented in the same code base, so with every customization our core would grow bigger and bigger….and therefore MDS seems not to be an option.

So the main question is how to approach customization? Are we making the wrong considerations? Are there other options?

The ideal world would give us the possibility to have our core packaged in a separate install (ear file), and to lets us do our customizations in a separate workspace with the possibility to override most of the core functionality… Is this possible within ADF?

Note: for clarity we are not speaking about personalization here (and the MDS possibilities for this issue).

Regards,
Gert