Building Components in Adobe CQ5 – Part 2: A tutorial on jQuery, AJAX and Sling

In this second part of my series on CQ5 and jQuery, we are going to look at how to implement a jQuery plugin that uses AJAX to retrieve data from CQ5. I picked the jQuery Flexigrid plugin as the target for this tutorial. The sample that we will build is to retrieve a list of users from CQ5 and display that list in the grid component. Although this is use case is meant for an administrative type of task (we would not display a list of registered users to just anyone), I picked this scenario because it will show us how to execute a callback to a JSP in CQ to retrieve JSON formatted data. Since the default Sling servlet in CQ5 can be used to retrieve content in JSON format for pretty much anything, I wanted to chose a fringe use case where we would not use the default Sling servlet and provide our own request handler so we can understand the complete workflow involved.

This tutorial builds on the first part of this series. We are going to create a new component in our /app/samples tree and re-use the clientlibs approach to include the required Flexigrid client files. We’ll then create a component JSP that will render the Flexigrid plugin. Once the grid is displayed, it will make a callback to the server to fetch the data to be displayed. This will be the focus of this post – showing how to configure and handle the call back through Sling and return the result of a CQ5 API call (get all users) in JSON back to the grid to be displayed. Continue reading…

How to change the default authenticator in CQ5

 

,,

While working with a colleague a few weeks ago, we ran into an interesting challenge with the site he was buillding in CQ5. This new site required a login page that needed to match a specific style. What we kept seeing the default Geometrixx-styled authenticator would be displayed. Now, we could have used our css file to override the look of the default authenticator. But we needed to add additional elements to the authentication page and wanted to manage the changes within the /app tree.

After some digging, I found that the authenticator hendler setting is not where I expected it to be (as part of the JCR properties of the site), it’s a sling:OsgiConfig propery located within the /libs tree. The full path is:

/libs/cq/security/config.publish/com.day.cq.impl.LoginSelectorHandler

Continue reading…

Building Components in Adobe CQ 5 – Part 1: A tutorial on clientlibs using jQuery UI

This is the first part of a multi-part set of posts I want to write about some things I learned while building a component in CQ 5.4. Since I struggled in a couple of areas, I am sure some of you have or will too.

In this first post, I want to cover the use of the clientlibs functionality in CQ. As you will gather from visiting the link, there is not much detail on this very useful capability. As you may or may not know, CQ renders web pages using Apache Sling. The tag line says “Bringing back the fun” … More on my take on that in a later post. Ultimately, pages are displayed by first locating content in the repository and based on the content’s resource type and selector. These resolve to scripts that actually render the content. The bottom line is that the final page that you see in your browser is made up of the output of multiple scripts that generate the HTML and associated assets.

This is where clientlibs comes in handy. Let’s say you would like to use a jQuery plugin from jQuery UI or some other plugin like Flexigrid in a custom CQ component, the key requirement is to include a combination of CSS and JS files in your page. Of course, in CQ you can edit the page templates of your site to include those libraries required for your component. But this is not best practice for a couple of reasons:

  1. Including client-side files in templates increases you page overhead because these files will be included in all pages wether they are needed or not;
  2. Your component will not be portable. If your rely on includes in the templates, then if you wanted to re-use your component on some other instance of CQ, you would have to manually touch the site templates to ensure that your component has the necessary libararies.

Clientlibs is a convenience wrapper for the com.day.cq.widget.HtmlLibraryManager service interface. This service manages the client side includes like JS and CSS to manage which files should be included in the page as well as ensuring duplicate files are not sent. Using clientlibs in CQ is really easy, once you know what to do that is :).

Here is a short tutorial to walk through what you need to do to. The example we will use, is include the jQuery UI files to leverage the dialog plugin to display a jQuery modal dialog.

I am using CRXDE to create the project.

  1. Create a basic component project in CQ. I am assuming you know how to do this. If not, have a look at this tutorial. I called my component myjquerysample. The project should look like this:
  2. Create a new cq:ClientLibraryFolder node under myjquerysample called clientlibs.
  3. Set the following properties on the new node:
    Name Type Value
    dependencies String[] cq.jquery
    categories String[] jquerysamples

    The dependencies property tell the CQ rendering engine to make sure that the jquery libraries are included in the page. CQ uses comes with jQuery libraries as they are used for built-in product features. CQ 5.4 comes with jQuery version 1.4.4. The categories property is absolutely key. We’ll see in another step that the category name will be used to tell CQ which clientlibs must be included with the html that will be generated. In fact, the cq.jquery we specified for the dependencies is a category. CQ uses categories to identify client libraries to be included.

  4. Select and download the jQuery UI components from jqueryui.com. Again, I assume you know how to do this. Here is a screenshot of the options that I chose. Extract the zip file you downloaded. In the extracted folder, you will find a folder named js and another css.
  5. Open the css folder on your file system. Since I selected Cupertino as the theme for the jQuery UI components there is a sub folder called cupertino. Open that folder to drag and drop its contents (css file and images folder) into the clientlibs node in CRXDE.
  6.  On your file system, navigate to the js folder to drag and drop the jquery-ui-1.8.16.custom.min.js to the clientlibs node in CRXDE.
  7. Your clientlibs folder should now look like this
  8. Now we need to tell CQ which files we would like to have picked up when our component gets rendered. To do this, create two additional text files in the clientlibs folder: js.txt and css.txt.
    The contents of the js.txt file (I am sure you have guessed already) is the filename(s) of javascript files you would like to include with your component. In our case, we need to reference the jquery-ui-1.8.16.custom.min.js.
    We need to do something similar to the css.txt file. The difference, is we are pointing to the jquery-ui-1.8.16.custom.css file.
  9. OK, now we have everything in place in our clientlibs node. Let’s create a simple component that displays a modal dialog with a message defined by the content author. To accomplish this, we’ll first need to create a dialog node that will enable the content author to provide a string which will be the message to be displayed in the dialog.
    Again, I assume you know how to do this. Here is a screenshot of my dialog tree as a reference.
  10. Now comes the final part, writing the script that will render the component’s content. This is where is all comes together.
    <%--
    My jQuery Sample Component component.
    Author: Marcel Boucher]
    --%>
    
    <%@include file="/libs/foundation/global.jsp"%>
    <%@page session="false" %>
    
    <%
    // Retrieve the configuration property if set by author.
    final String message = properties.get("message", "Default message for dialog.");
    %>
    
    <cq:includeClientLib categories="jquerysamples" />
    
    <script type="text/javascript">
    // This function is called on page ready. It bootstraps the div tag with the jQuery UI dialog class.
    jQuery(function ($) {
    $( "#dialog-message" ).dialog({
    modal: true,
    autoOpen: false,
    buttons: {
    Ok: function() {
    $( this ).dialog( "close" );
    }
    }
    });
    });
    
    // This function actually displays the dialog sets the title and body.
    function showDialog() {
    jQuery( "#dialog-message" ).dialog( "option", "title", 'My Sample jQuery UI Dialog' );
    jQuery("#dialog-message p").html("<%= message %>");
    jQuery("#dialog-message").dialog("open");
    }
    
    </script>
    
    <button id="btnShowMessage" onclick="showDialog()">Show jQuery dialog</button>
    
    <!-- Placeholder for the jQuery UI Dialog plugin -->
    <div id="dialog-message" title="My Sample Dialog">
    <p>Default message content.</p>
    </div>
    

There is a lot going on here. The key line in this code is the <cq:includeClientLib> JSP tag. We tell CQ to include all clientlibs folders that have been tagged as being part of the jquerysamples category. This also means that you could create shared clientlibs across multiple components.

,,,

Real time notification using Data Services in ADEP

With the release of ADEP and the introduction of the Experience Server (RIA services running on the CRX stack), developers now have access to data services within the ADEP Core (previously known as CRX) runtime. This means that data services components are now deployed as OSGi bundles in ADEP Core. This opens now interesting doors for developers building content-oriented applications (like the app we will build during my BYOD lab at MAX), In this post, I will talk about how you can leverage the data services MessageBroker to push notifications to Flex clients that a node in the ADEP content repository has been added or changed. There are many use cases for this functionality, but the one I find most exciting is in the context of mobile application development.

Imagine you have a mobile application that displays information stored on a server – of course we would use the ADEP content repository for this! The challenge for these types of applications is how to keep that information in sync. Make the user request a refresh? Build some sort of polling mechanism? Why? ADEP data services already has this problem solved with real time messaging. The trick is how to send a message when a node is updated or added (or even removed) in the repository. Before we tackle this challenge, lets look at how data services has been adapted to run within the ADEP Experience Server.

If we are going to use data services messaging, we have to add a destination to the messaging-config.xml file right? You bet, but where can we find this file? Not on the file system that’s for sure. We have a content repository now. The best way to access the content repository as a developer is either to use CRXDE (an Eclipse-based IDE for ADEP Core) or CRXDE Lite (a web-based IDE for ADEP Core). For the purposes of this post, I am going to use CRXDE Lite.

Step 1: Creating a destination

To access CRXDE Lite on your machine (running ADEP Experience Server or the ADEP Solution Quickstart) navigate to http://localhost:4502/crx/de/ in your favorite browser. In this image, you see that the JCR repository path is /etc/aep/config/dataservices/messaging-config.xml.  You can open the file by double-clicking it. You will notice something different about the include statement in the file. Instead of pointing to a specific file to include, we are pointing to a repository folder location.

<destination-include directory-path=”destinations/messaging”/>

What this means is that the MessageBroker will iterate through each file located in the destinations/messaging folder and read in the configurations it finds. Which means that if we want to add a new destination, we need to add a new file in this location. In my sample, I created a file called com.adobe.tm.flex.broker.messaging-config.xml the contents of this file is typical destination config information:

Step 2: Loading configuration changes

This defines a new destination called “flexmobile“. The only thing left to activate this new destination is reload the MessageBroker class. In the J2EE version, this means bouncing the app server or some other weird incantation. Remember that in ADEP, Data Services is now a set of OSGi bundles.To reload MessageBroker to pick up the new configuration changes, we need to access the OSGi (Felix) console. On your machine, navigate to http://localhost:4502/system/console/configMgr. Click on the Components button in the toolbar and locate the following class: com.adobe.dataservices.impl.ApplicationManagerImpl

In the actions column, click on the stop icon. Once the component has stopped, the icon will change to a play button. Click it again to start the component. This will trigger the MessageBroker class to reload the configuration xml files.

Step 3: Creating a bundle to send a message via MessageBroker

Back to our original scenario. We want to send a message via message broker when a node in the content repository changes. A very simple approach to setting this up is to use the ADEP WEM workflow engine. It has built-in launchers that can be triggered by content repository node events like update, add and delete. What I did in this case, was build an ADEP OSGi bundle that implements the workflow execute() interface so it can be called from a workflow process. Since this post is not meant to be a complete tutorial on how to develop on ADEP Core, please refer to Defining a Process Step: with a Java Class for detailed steps and examples. Here is the code from the bundle class:

/*
 * @author: Marcel Boucher (mboucher@adobe.com)
 * MessageBrokerProxy class
 * This class is an implementation of a customer workflow step that can be used
 * to publish messages to the dataservices message broker running on this server.
 * 
 */
package com.adobe.tm.wf.dataservices.messaging;

import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

import flex.messaging.MessageBroker;
import flex.messaging.messages.AsyncMessage;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;



@SuppressWarnings("unused")
@Component
@Service
@Properties({
        @Property(name = Constants.SERVICE_DESCRIPTION, value = "Send Message to Data Services MessageBroker."),
        @Property(name = Constants.SERVICE_VENDOR, value = "Adobe"),
        @Property(name = "process.label", value = "Send to MessageBroker")})


public class MessageBrokerProxy implements WorkflowProcess
{
        private static final Logger log = LoggerFactory.getLogger(MessageBrokerProxy.class);
        private static final String TYPE_JCR_PATH = "JCR_PATH";

        public void execute(WorkItem item, WorkflowSession session, MetaDataMap args) throws WorkflowException 
        {
            //Collect Message information from the dialog.
            String destination = args.get("destination","not set");
            // Default Messagge.
            String messageBody = args.get("message", "not set");
            
            //Set the message to contain the path of the node that has changed.
            WorkflowData workflowData = item.getWorkflowData();
            if (workflowData.getPayloadType().equals(TYPE_JCR_PATH)) {
                String path = workflowData.getPayload().toString() + "/par";
                messageBody = path;
            }
            
            //Create a new message.
            AsyncMessage msg = new AsyncMessage();

            msg.setClientId("ADEP-Experience-Server");
            msg.setTimestamp(new Date().getTime());
            //you can create a unique id
            msg.setMessageId("ADEP-Experience-Server-Message");
            //destination to which the message is to be sent
            msg.setDestination(destination);        
            //set message body
            //msg.setBody(messageBody != null?messageBody:"");
            msg.setBody(messageBody);
            //set message header
            msg.setHeader("sender", "From the server");

            //send message to destination
            MessageBroker.getMessageBroker("__default__").routeMessageToService(msg, null);
          
    }
}

Notice that the getMessageBroker() uses the __default__ argument which is to indicate the instance currently running in the same container.

Step 4: Create and configure the workflow

The next step is defining a workflow that will invoke our custom class when something in a specified node tree changes. You can access the workflow console on you machine using this url: http://localhost:4502/libs/cq/workflow/content/console.html

In the Models tab within the Workflow Console, click the New button and give your new model a meaningful name. For example “Post to Message Broker”. Double click on the newly created model to edit it. Delete the default user task from the model and add the custom step you created in the previous step from the Workflow group in Sidekick. You can double click the step to access it’s property dialog and configure additional settings. When you are done editing, click the Save button to return to the Workflow Console.

That’s it for the workflow model. All we want it to do is call our custom class to post a message to the MessageBroker.

The next thing we need to define is how this new workflow model will be launched. From the Workflow Console, click on the Launcher tab. You will see a significant list of launchers that are pre-configured in the system. We are going to add a new launcher, so click on the Add… button. Complete the launcher configuration settings and click OK.

ADEP will now monitor the node located in the path provided and launch the specified workflow when that node is modified.

The next step is to build a Flex application that subscribed to the MessageBroker destination and handles the messages it receives.

Step 4: The message consumer

Create a typical Flex application, you will need the FDS.swc library of course to use messaging. Simply define a consumer like you would with regular data services


<span><<span>mx</span>:Consumer id="consumer"</span>
<span> destination="<span>flexmobile</span>"</span>
 message="messageHandler(event);"
<span> fault="<span>faultHandler</span>(event);"/></span>

All you have to do now is build your handler functions and you now have real time notification of ADEP content repository changes. Go on and start building your content-driven applications.

My BYOD Lab at MAX 2011: Content-Oriented application development using ADEP and Flash Builder.

This year at MAX, I am going to lead a BYOD (Bring Your Own Device) lab. The title of the lab is Content-Oriented Application Development using ADEP and Flash Builder. I know it’s a mouth full of a title, so here’s a simple description of what this lab will cover.

There are two technology parts to the session:

  • ADEP’s Web Experience Management Solution: With the acquisition of Day software, Adobe has re branded and packaged the CQ5 web content management product into the ADEP offering and is now known as Web Experience Management Solution. We will leverage this solution in the lab as a best in class platform for managing web content in a JCR (Java Content Repository). We will take this concept to the next level by not just managing web content, but also managing application functionality. Using an intuitive and fun to use web-based authoring environment, content owners and application specialists are able to publish new content without having to rely on IT. This leaves IT with the ability to focus on more pressing development projects.
  • Flash Builder 4.5.1: We will use Flash Builder to create a mobile application that queries the ADEP content repository for information that should be displayed within the application. We will learn how to parse the result using JCR APis and easily render the content to screen. While this is cool, You’ll really be blown away as we use a very similar approach to use content defined in the content repository to control what views are displayed in the mobile application.

By using this content-driven application development approach for mobile, you will now have the ability to enable content owners and application specialists to manage defined parts of your mobile apps without having to recompile code and re-distribute the apps to the various application stores!

Here is a recording I made that showcases what we will be building, check it out.

If you have not registered for this session, WHAT ARE YOU WAITING FOR? :o)

 

 

,,,

TIP: Transitioning Flash Builder ADEP tooling from ADEP prerelease to release build

,,

Like any other software package that moves along a release cycle, final tweaks were made to ADEP that can cause some inconsistencies for those of us that used a previous builds. In this case, I have found one of these inconsistencies while using the latest Adobe Enterprise Suite Extensions in Flash Builder 4.5.1 to create a Composite Application Framework (previously known as Mosaic) project.

When you create a Composite Application in Flash Builder, you will notice that a new folder called catalogs will be added to your workspace. This folder is a local “cache” of the Composite Application Framework catalogs that are stored in the ADEP experience server repository. When you install the experience server, you automatically get a default catalog aptly named default.cxml. Of course, you can create multiple catalogs for your Composite Applications - but that’s for another post.

 

If you have used previous prerelease builds of ADEP to create Composite Applications using Flash Builder, you will have this catalogs folder in your Flash Builder workspace. When you upgrade to a newer ADEP build, you need to delete this folder from your workspace before attempting to create a new Composite Application. One of these tweaks that has occurred between builds is that some tile definition attribute names have changed in the cxml file. Therefore, if you continue to use the old cxml format and try to deploy a new Composite Application, an error will be displayed in Flash Builder. The error will likely refer to the use of an invalid attribute called initialHeight - which has been changed to height.

Of course, deleting the catalogs folder means that existing Composite Applications you may have created in previous builds will no longer be registered in the repository. If you would like to keep your existing applications, then you would simply have to manually append your applications and tiles to the new default.cxml. Just be mindful to change the appropriate attributes like intialHeight to the new names. Don’t worry, the default.cxml has a commented block at the beginning that describes all of the attributes you may need.

How to configure white list for the new CSRF filter in ADEP

,,

Today, I installed the latest build of the Adobe Digital Enterprise Platform (ADEP) on one of my servers. I remotely administer this server - so once the installation was complete, the first thing I wanted to do is access the administration ui (adminui) using my browser to perform additional configurations. The URL is http:<fully_qualified_server_name>:8080/adminui. To my surprise, I received a denied access message when I attempted to log in. If I used the browser on the same server I just installed ADEP, it worked fine. I know what you are thinking… firewall blocking the port right? Nope. I have used this server for previous versions of LiveCycle ES and had opened port 8080 previously. Furthermore, if I used the server’s IP address, I could successfully access adminui. Now that’s a head scratcher! Let’s see what the application server log file has to say:

2011-08-05 12:32:08,898 WARNING [com.adobe.idp.um.auth.filter.CSRFFilter] (http-0.0.0.0-8080-1) Blocked request for resource:/adminui/login.faces due to invalid referer:http://<fully_qualified_server_name>:8080/adminui/login.faces. More information is available at http://www.adobe.com/go/learn_lc_hardening_10

What’s this CSRFFilter thing??? After a little digging I found out that in ADEP we have implemented a Cross Site Request Forgery (CSRF) defense filter. The ADEP CSRFFilter ensures that ADEP resources or URIs such as /adminui and /workspace can only be accessed through predefined referers (address of the source page requesting access to the resource). The goal is to prevent malicious requests riding piggy back on an authenticated browser session. During the installation process of ADEP, specifically in Configuration Manager, there is a system bootstrapping process that occurs. During this process, the ADEP document server determines the fully qualified host name of the server you are installing on and adds it (as well as IP address and localhost references) to the whitelist automatically. In ADEP, you can access the whitelist configuration page by logging into adminui and navigating to: Settings > User Management > Configuration > Configure Allowed Referer URL’s.

Why did it not work for me then? If the host name is automatically added to the whitelist, why was my request denied? The issue was actually cause by how the Windows machine name and domain configuration is set when I joined the corporate domain. The domain I joined was (I’ll leave out actual details to be sure the IT guys don’t hunt me down) ourdomain.xyz.org.com, which means that the fully qualified name is myserver.ourdomain.xyz.org.com which is what was automatically added to the whitelist during the bootstrap process. Here’s the rub… The way the network is setup at Adobe, that’s not the domain name we use in a URL to access our servers. The suffix we use is geo.org.com, so I would hit my server with http://myserver.geo.org.com:8080/adminui which of course is NOT in the whitelist. To correctly configure my ADEP server, I simply added that qualified server name to the whitelist and restart the application server. Yes, a restart of the application is required for the change to take effect. In case I forgot to mention it, remember to restart the application server :o). As I am sure you can tell, I did forget.

I doubt many people will run into this anomaly, but better be on the safe side.

Context is King!

,,

Last week in Washington DC, we began the first in a series of training events for our technical sales field. We delivered quite a few sessions on our upcoming release of Adobe Digital Enterprise Platform (ADEP) as well as some sessions on user experience. One of the things that struck me throughout the event is the divergence of perspective when looking at Customer Experience Management.

On one end of the spectrum, you have the warlords of enterprise transactions. This group of people are desperately hanging on to the database transaction-driven development approach. I found an interesting post by Dennis Howlett that kind of reflects this perspective – Enterprise 2.0: It’s not about people it’s about the process. For them, transaction is king. The reward is in the elegance of the back-office integration while the user experience is a secondary concern. At the other end of the spectrum, we have the "user experience (UX) needs to take over the enterprise" society. For this group, the consumerization of IT is the mantra. Designing the ultimate experience is the catalyst for making enterprise applications bearable at a minimum. Of course, I am being a little excessive. For them, content is king. Success is defined by making content (be it a form, text or digital asset) intuitive and fun to interact with. What happens when these two groups "collide" on a project? Tension, conflict, miscommunication and turf wars.

For Customer Experience Management, I truly believe that neither transaction nor content is king – context is king! With the explosion of devices such as smart phones and tablets, an additional dimension has been added to the complexity of enterprise application development. Earlier this summer, Christophe Coenraets and I were talking about this new application paradigm as it applies to mobile. We discussed how the decision matrix is no longer just two dimensional by representing static vs dynamic on the x axis and then single vs multi-channel  on the y axis as shown below (the dots represent the many combinations of where enterprise applications may find themselves).

In fact, the decision matrix that is now facing enterprise application developers has a third dimension – context. As we mobilize our digital world, people have a reasonable expectation that enterprises have the ability to keep track of their individual context. If I access a website to do some research on a particular product or service, I fully expect that when I access that same organization’s mobile application that they will know what interests me and tailor the experience with that information in mind. And if I walk into a store or branch, the customer facing employee should also have access to that information to deliver the best experience possible.

It’s no longer enough for organizations to simply focus in whiz-bang UX for the web or focus on a seamless transaction into the IT backbone. Customer Experience Management  – specifically Adobe CEM – transcends the single points of contact. It is all about embracing the diversity of customer interactions while removing the burden of context from the customer and placing it where it belongs, in the application tier. Yes, context is in fact king.

This is why I am so excited about the Adobe Digital Enterprise Platform. Having a single platform that provides a shared context and a shared repository while providing the experience and document services required to build this next generation of enterprise applications, enables developers to meet this new challenge and still keep all their hair (or whatever is left of it in my case) in place.

Adobe Execs talk about ADEP

,,,

Watch Rob Tarkof, David Nuescheler, Alex Choy and Kevin Cochrane talk about Customer Experience Management and the Adobe Digital Enterprise Platform release on YouTube.

Announcing Adobe Digital Enterprise Platform

Today, we announced the Adobe Digital Enterprise Platform. This is a very exciting time for Adobe and every organization looking to deliver customer experience solutions that truly meet market requirements. Too many times software companies talk about things to come and how they can make it better. Customer Experience Management is not a future trend, the time is now. The Adobe Digital Enterprise Platform is built to enable developers to deliver the best multi-channel experience possible – whether it’s HTML5, Flash or AIR.

You may be asking yourself, so what’s the big deal about ADEP, I could do the above with Flex and jQuery, etc. The big deal is that with the combination of CRX (from the Day acquisition) and LiveCycle ES which is now ADEP, you now have a modular platform where web scalable capabilities such as data services and composite application framework (previously known as Mosaic) run on the lightweight Experience Server core (CRX) and enterprise scalable capabilities like process management and document services run on the Document Server on JEE(LiveCycle).

Add that to what you can now build in Flex 4.5 for mobile and browser as well as HTML5 within dreamweaver, you now have one killer platform and best of breed tooling to leverage your existing IT investments.

Now that we have reached public announce, you will see a lot of backlogged information hit the bloggosphere pretty fast. Actually, I am hosting two sessions this week: one on Tuesday as part of the Developer Deep Dive sessions and another on Thursday as part of Flex Developer Week. You can also check out the new CEM website for more information on the awesome solutions that we will be releasing on top of ADEP. Rob Tarkoff also posted on the Adobe blogs an excellent overview.

Of course we will be showcasing all of this great stuff at Adobe MAX 2011and for the first time, we will also be hosting our first Digital Enterprise Summit October 3-4 in LA as well.

Stay tuned, we’re just getting started!