Webcenter Interaction Blogs (aka Aqualogic Interaction)

July 02, 2009

Terry Wang

Can't apply patches for Oracle BPM (ALBPM) Studio on Windows?

The first hot fix and service pack for Oracle BPM 10gR3 were released. If you do a quick search on Google, you'll surprisingly found that it is still published on BEA web site for some reason.

I did noticed that folks run into problems applying the hot fix. I am not sure if there is any customer out there who is using this release. However, as it is first release under Oracle, it shoud have drew enough attention of the public. Besides, bloody Windows still owns almost 80% of the OS market. So I reckon that this post won't be a waste of time anyway.

Finally we got something officially, this is a known issue for Studio(Windows only).

Symptom: Errors will be encountered when applying hot fix for OBPM studio 10gR3 GA.
"Files in use by another application. Stop all BPM applications before installing this update.
Detail:The following file could not be modified: C:\Oracle\BPMStudio\lib\fuego.boot.jar."

Solution:
1. shutdown Studio
2. download the fuego.boot.jar(MP0 hotfix) or fuego.boot.jar(MP1) file listed in the download website for your particular update.
3. copy the downloaded fuego.boot.jar file to studio_installation_dir/lib
4.start Studio
5. apply either the HF

Note: There are 2 different fuego.boot.jar files. This needs to be done only once for 10gR3 GA. After you have applied the hotfix or or pactchset(MP), you don't need to do it again.

Check Help - About for version info: 10.3.0.0.0 Build #94551 or 10.3.1.0 Build #94375

For more info, check this out:
http://albpmsupport.bea.com/download/10gR3/10gR3Studio.jsp

More updates:
If you try to upgrade from ALBPM 6.0 MP4 to MP5, you'll probably run into the same issue.


"This HotFix cannot be applied to this product version. Detail: Current Version: 4 - HotFix Service Pack Version: 5".



The workaround is to shutdown Studio, copy the fuego.boot.jar file from STUDIO_INSTALL_DIR/studio/studio/eclipse/plugins/fuego.boot_6.0.0 to STUDIO_INSTALL_DIR/lib, overwrite the one there. Start Studio and apply the hot fix again.
replace the one in /lib

by Terry Wang (noreply@blogger.com) at July 02, 2009 12:53 AM

June 24, 2009

PlumtreeG6.com

Hello world!

Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!

by admin at June 24, 2009 07:40 PM

June 18, 2009

Terry Wang

Too Many Open Files on Ubuntu Linux when installing Oracle BPM

Recently I am working on VM images for test a test environment based on the new Oracle BPM 10gR3 release and the OS has to be Linux for some reason.

Environment
OS: Ubuntu 8.10 Intrepid Ibex 32-bit
Kernel: 2.6.27-7-generic
JDK: Sun JDK 1.6.0_10
Weblogic Server 10gR3 on JRockit 1.6.0_05 (R27.6.0-50 linux ia32)
(I use the Oracle Service Bus 10gR3 install media which includes WLS)
Oracle 10g XE


We can do it thought in the official interoperability matrix this is not a supported combination.
Interoperability Matrix:
http://www.oracle.com/technology/products/bpm/obpm_config_matrix.html

Note: It's better to use root user to install OBPM and WLS, or we may have permission problems.

  1. Launch admin center using root, just use su -s to avoid typing sudo all the time. If not we may encounter permission problems.
    /opt/OracleBPMwlHome/bin/./obpmadmcenter

  2. Click configuration, in Directory tab, click add to create directory. I don't want to talk all the details, please refer to the official installation guide for more details:http://download.oracle.com/docs/cd/E13154_01/bpm/docs65/config_guide/index.html

    Normally this will probably a painless process (actually it was on Windows as I did it a few days ago)

    In this case, the Configuration wizard hang at around 70%, like in the screenshot.

So I checked the WLS logs as well as the Adim Center logs. It seems that errors were encountered when executing WLST. Errors in Admin Center log:

java.io.FileNotFoundException: /opt/bea/user_projects/domains/bpm/config/config.xml (Too many open files)

This is simply caused by the default max open files (File Handlers) setting on Linux OS.

Normally the default max open files is 1024. This small limit (which appears to be the default value in most linux environments) might be a problem for WebLogic Server deployment and so we recommend increasing it.

Session Wide Solution:
Increase the limit of open files using the command ulimit -n 2048 in the session you start the obpmadmcenter or WLS domain (if you choose to modify an existing domain)

Use ulimit -a to check the new limit. This is just a temporary fix, only for the current terminal session.
Then run the config wizard again and I think you can get there.

Make the change permanent:
Change the file /etc/security/limits.conf (root)
The following line must be appended to the file:
$user hard nofile 2048

$user is the user that starts the WLS
2048 is the recommended limit but we may need to increase again if the issue recurs.

Start Admin Center, start BPM Web applications, then Weblogic Server. Now you can login to Process Administrator (webconsole) and Workspace and see the sample process if you selected the option.

Alternative workaround(from Official Debian Documentation and Oracle Technology Network):

make the change without rebooting the server box

sysctl -w fs.file-max 65536
sysctl -a
(echo "65536" > /proc/sys/fs/file-max)

To make this change permanent by inserting the kernel parameter in the /etc/sysctl.conf startup file:
# echo "fs.file-max=65536" >> /etc/sysctl.conf

To query the current usage of file handles by using the following:
# cat /proc/sys/fs/file-nr
825 0 65536

Difference between changing the resource limits via ulimit and sysctl:
basically the priority is: soft limit < hard limit < kernel limit

The Linux kernel provides the getrlimit and setrlimit system calls to get and set resource limits per process. Each resource has an associated soft and hard limit. The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may only set its soft limit to a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard limit. A privileged process (one with the CAP_SYS_RESOURCE capability) may make arbitrary changes to either limit value.

Tips: Install OpenSSH for easy management as a VM. If you prefer Web UI, try webmin it is also a good tool for overall management. I install webmin on each of my Ubuntu Server VM or box.

References:

by Terry Wang (noreply@blogger.com) at June 18, 2009 12:46 AM

June 16, 2009

Function1

Have you seen Watcher?

So you, the Plumtree / AquaLogic / WebCenter admin, get a call from a user indicating that Collab email notifications are not being sent (a plausible symptom that the Collab Notification service is in "stopped" status), or that Publisher is not responding (an indication that the Publisher service might also be down), alternatively Portal searches yielding no results (a sign that the search server is down) ... the issues that a Portal admin can encounter are just myriad.

 

Ideally, we shouldn't be notified from our users that an issue has occurred.  Granted there are a number of monitoring software in the market; e.g. Microsoft Operations Manager that monitors disk space, memory and CPU utilization, among other tasks, presenting the results in console like dashboards, yet we haven't seen a monitoring software dedicated to guarding a Portal deployment alerting its admin staff of potential issues.  Hence, the impetus for Watcher, a software monitoring tool dedicated to watch a Portal installation.

 

Watcher is created to run different types of monitors (currently 10 different rules) for a typical Portal install; these include:

 

1.      "Service" monitor to check if a particular daemon service has stopped and it attempts to restart it.

2.      "File Size" monitor to check if log files have reached a particular threshold size and ensure that the Portal admin is alerted if the size is within a predefined threshold.

3.      "Disk Size" monitor to check if a hard drive has reached a certain threshold size.

4.      "Directory Size" monitor to check the size of a folder.

5.      "File Text" monitor to check if a specific text string has occurred within a log file.  That can be useful to skim through a log file and search for a particular error string.

6.      "HTTP" monitor to check that the Portal URL is responsive and is returning a response within a given time threshold and that the HTTP request status code is 200 (OK).

7.      "Ping" monitor to ping a given host and ensure that the host is reachable within a given time threshold.

8.      "TCP" monitor to telnet a given host and send a command and parse the response.

9.      "Database" monitor to send different SQL statements and validate the result again specific thresholds; e.g. number of records in the ptUsers table should not be less than an "x" number thus indicating that the Active Directory Web Service has successfully executed.

10.  "Search Server" ensuring that all search nodes are in "Run" state.

 

The Watcher architecture is comprised of three components:

 

1)      A lightweight agent deployed as a service with a low footprint in terms of CPU and memory utilization running different monitors that can are configured as needed; i.e. different types of rules.

2)      A server console that receives the monitoring results from the different agents via HTTP requests.

3)      A notification service that alerts the admin of issues if a particular issue or threshold is encountered.

 

Watcher is shipped with preconfigured rules that can be further tailored as needed.  It can also be configured to monitor non Portal related services, database instances, hosts, URLs, etc.

 

To receive further information or schedule a demo, please contact us at info@function1.com

 

 

Thanks.

 

Hani

 

June 16, 2009 10:30 PM

June 14, 2009

Terry Wang

Skype Sound Devices settings on Ubuntu (Dell D620/D630)

There are so many different sound architectures and solutions on Linux, like OSS, ALSA and Pulse audio. It seems that ALSA is the dominant one and Pulse is something new to take its place, not sure yet. It is very confusing and frustrating for new users.

I have had bad experience setting up sound input and output with OSS and ALSA earlier. It is not as easy and pleasant as on Mac and Windows.

I am a huge fan of Skype, as the PC to PC call is free and the voice/video quality is excellent. Also because it is cross platform, it is available on iPhone (Skype for iPhone and Fring) and portable devices powered by Windows Mobile or Symbian (e71 using Fring), PSP..., probably more to come.

1 of the most important point to use it is that communications between clients and servers are encrypted by AES without any configuration. So all text and voice chats are secured basically until it reaches PSTN.

For more info:
Why Are Skype Calls encrypted?
Are my Skype calls secure or encrypted?
What type of encryption is used?

Other IMs like Google Talk also offers communication over SSL, but it requires configuration on different client tools like Pidgin, Empathy, InstantBird, Adium or official client. So Skype is still the easiest tool for average crowd.

In fact I switched to Ubuntu from Fedora Core 5 in mid 2006, installed on Dell Latitude D620 which was my working laptop. It went smoothly and I was pretty happy with almost everything except the sound input and output.

I tried to get the built-in mic work in Hardy and Intrepid, but for some reason, I didn't make it. I can hear others talking but they can't hear me because I didn't seem to have the right Sound devices settings for Skype.

After upgrading to 9.04 Jaunty Jackalope in April, I tried to call my wife to test Skype. Holy cow! I found that the Dell Latitude D620 built-in microphone was working with the same Intrepid settings.

Check the settings below, it has been tested and proved to be working on Dell Latitude D620 and D630 with the built-in mic.

Hope this is useful to those who has trouble setting Skype sound devices.

References for Linux sound solutions:
OSS Open Sound System
ALSA Advanced Linux Sound Architecture
PulseAudio

by Terry Wang (noreply@blogger.com) at June 14, 2009 12:02 PM

June 10, 2009

Brian Harrison

The Zen of the WCI 10gR3 Upgrade

First of all, let me apologize for taking so long to come back with a new blog entry. Things have been pretty busy over the last few weeks and that has prevented me from getting a new content out to you.

I want to once again step away from the sizing discussion for a second and talk about something that I get asked about all the time. Since the Oracle acquisition, I get asked all the time whether a customer should upgrade to WCI 10gR3 and if so, then how long should it take. Like a lot of questions that I get asked, the answer is not extremely straight forward. So let’s look at both pieces of the question.

Should I Upgrade to WCI 10gR3?

Surprisingly, this is not an easy question to answer. If you are currently running a version of ALUI 6.5, then there is truly not a great reason for upgrading, because there were not a lot of changes made between ALUI 6.5 MP1 and WCI 10gR3. In fact, we essentially think of WCI 10gR3 as ALUI 6.5 MP2. Here is a list of the primary changes that were made to create WCI 10gR3:

• New Adaptive Layouts for iPhone
• New Experience Rule for IP addresses
• Oracle Branding
• Removal of license key requirements
• A few bug fixes across different areas of the products

If one or more of the above items are important, then there may be a good reason to upgrade to WCI 10gR3. However, if the above items are not important, then maybe it isn’t important for you to upgrade quite yet.

On the other hand, if you are currently running a version of ALUI 6.x prior to 6.5, then there are a lot of great reasons for upgrading because there were a lot of new features added between 6.x and WCI 10gR3. The new UI engine called Adaptive Layouts is definitely a very significant improvement as well as the availability of the new Friendly URLs feature. In almost every instance, I would recommend that a ALUI 6.0 or 6.1 customer upgrade to WCI 10gR3.

How long will it take to upgrade?

There is actually no definitive answer to this question that can be given to every single customer. Every customer is going to have different testing requirements. Those testing requirements are also going to be different based on the type of deployment that the customer has. Even though I can’t provide a definitive timeframe, there are some pointers that I can provide for what areas should be focused on prior to the actual performance of the upgrade.

1. Database – This along with the running of the installer is probably the only required piece that all customers will need to look at because there is definitely a set of scripts that you will have to run. Once the product has been installed, a set of database scripts will be available in the sql folder for your environment. Depending on what version you are upgrading from, you may have more than one script to run through, but the good news that this is the easiest part of the upgrade.

2. Installer – This is obviously required for any upgrade, because it is the only way that you will get the latest version of the web application. Make sure that you have the portal completely shutdown prior to running the installer, this means all backend services and all application server services so that there are no locks on any of the files being upgraded. This should only take an hour or so per server to run through as long as you have all the form field entries written down and available.

3. UI – If you have created any kind of custom UI, either through the older Navigation APIs and UI MVC paradigm or using a header type of navigation, this is definitely something that you should look at prior to the upgrade. You may want to move to the new Adaptive Layouts and this could require quite a bit of work or at the very least you may need to recompile your UI libraries so that will definitely work after the upgrade.

4. Portlets – Depending on the portlets that you have developed, you may need to take a look at these and verify that they will work properly within the scope of the new version. Maybe you will need to recompile them because of a new version of the IDK or because you are using a native library and that library is being updated. You should definitely make sure that you run through your inventory and do some verification.

At the end of the day though, make sure that you have a lot of testing in your upgrade plan. You may even want to run through a mock upgrade within a development or testing environment prior to trying the upgrade in production, just so that you can be sure of all the gotchas.

The WCI upgrade is not meant to scare you as a customer, but it isn’t meant to be trivial either. We are trying to provide as many new features and bug fixes as we can in each new release. If you are concerned about your upgrade, then you should definitely speak with Oracle Consulting and they will be more than happy to help you out. They have a lot of experience with the upgrade process and as a former member of the consulting organization, I can definitely attest to that.

by brian.harrison at June 10, 2009 09:36 PM

Function1

Known limitations with the Web Center Interaction

Hey all!

Over the last few months, I've had 2 customers run into 2 separate limitations with the Oracle Web Center Interaction(WCI) product, namely in version 6.1 MP1.  The first of the two cases I had run into previously, so that prompted the idea for this post to share and build a running list of these limitations.  The second of the 2 cases led me to dig deeper to find the reason for these constraints.  What are these limitations you ask?

Well for example, if you're working with ALUI 6.1MP1, did you know that the maximum number of pages created within a community is 25?  Or that a community cannot be created within a folder more than 10 levels deep?

Some of you may be thinking: why would you want to create more than 25 pages.  In my customer's case, their requirement was to have more than 25 pages organized within a community but have the majority of the pages hidden in the navigation.

These fixed values are actually hard-coded in the portal server library (a.k.a plumtreeserver), which I coincidentally covered in my last post.  In particular, it's in the com.plumtree.server.PT_SERVERCONSTANTS class.  It seems as if Oracle is moving towards getting rid of some of these unnecessary constraints.  For example, the constraint on the number of community pages has been removed in 10gR3.  If you're on an earlier version or if you hit another similar limitation, you have 2 simple choices but only one is a viable long term one.  The first option would be to decompile the classes in the plumtreeserver.jar (or DLL for the .Net portal), modify the settings and rebuild the library.  However, this option is not ideal as this would complicate product upgrades and Oracle support contracts.

The best approach is to try to look for some other workaround for these constraints.  For the second limitation with admin folder levels, this still remains in 10gR3.  One possible solution is to create a flatter taxonomy so that more subcommmunity levels can exist.  If this isn't feasible or no other options are available, you may be faced with replacing the fixed value in the portal server library.  Again, we advise you do this only with extreme caution.  Please contact us if you'd like more information.

The initial idea behind this article was not the workarounds, but to create a running list of these "hardcoded" limitations within the WebCenter Interaction product stack and the respective workarounds.  I've started the list off, but please post your comments on other limitations that you have run into along with any solutions.  I'll be updating this post with all of the comments, so please submit any that you have found even if a workaround wasn't.


Portal Limitations

Limitation
   Workaround
Cannot create more than 25 pages within a community (in ALUI 6.1 MP1 or earlier)
   Create the pages in another community and move them into the target community.
Maximum number of MyPages is 6    None found yet!
Communities cannot be created within a folder which is "10 deep"    Restructure the taxonomy to gains some levels back
Maximum length for the name of a document in the knowledge directory is 255 characters
   None found yet.
Maximum folder level in the Knowledge Directory is 28    Restructure the knowledge directory taxonomy when the limit is reached.



thanks,

Vasanth

June 10, 2009 06:05 AM

Fabien Sanglier

Adding JavaScript includes to all portal browsing pages

In almost every portal deployment, we usually have the need to create some JavaScript libraries that can be used by various components…possible functions could be window openers, url encoder/decoder or any other utility functions that you might not want to include in all your portlets.

Now, the question is: how do you add it to the portal page as (a) global include(s)? 2 possibilities:

  1. Add the JavaScript include(s) to the portal header/footer portlets (which is usually done in publisher, and displayed on all pages)
  2. Create a small UI customization that does the job more reliably.

You might have guessed it now: option 1 is not the one I am going to explain here. Why?

First, option 1 would not be worth a blog post since it is fairly easy to implement. But more seriously, depending on header or footer portlets is not reliable in order to include global resources. Indeed, different portlet headers/footers are displayed based on the current experience definition, and/or the current community you are in. Also, the header/footer portlet content can easily be modified (especially if implemented in publisher for example) which increase the chances of removing the resource include(s).

Ok, let’s dive into option 2!

By customizing the 2 main browsing portal DP (stands for Display Page) classes (MyPortalDP and GatewayHostedDP), you will be able to include all the JavaScript resource you need on virtually all end user portal pages: all portal community pages and on the gateway page in hosted display mode.

Luckily, those DP classes provide a method to override: DisplayJavaScriptFromChild. This method returns an HTMLScriptCollection object that will be read in order to add the JavaScript to the header tag (in between the portal <head></head> tags).

The following could be what you might want to have in this method override (it gets the list of JavaScript files to include from a Varpack, and iterate through this list in order to add it to the returned HTMLScriptCollection object)

protected override HTMLScriptCollection DisplayJavaScriptFromChild()
{
HTMLScriptCollection scriptCollection = base.DisplayJavaScriptFromChild();
try
{
//get all the JS to include
XPArrayList arrJSToInclude = ...Getting this from Varpack is a good idea, and as such, recommended...;
if(null != arrJSToInclude && arrJSToInclude.GetSize() > 0)
{
IXPEnumerator jsEnum = arrJSToInclude.GetEnumerator();
string src = "";
while(jsEnum.MoveNext())
{
src = (string)jsEnum.GetCurrent();
if(!"".Equals(src))
{
HTMLScript script = new HTMLScript(HTMLScript.TYPE_JAVASCRIPT);
src = src.Replace("pt://images/", ConfigHelper.GetImageServerRootURL(m_asOwner));
script.SetSrc(src);
log.Debug("Adding JS external file with src: {0}", src);
scriptCollection.AddInnerHTMLElement(script);
}
}
}
}
catch (Exception exc)
{
log.Error(exc, "An exception occurred while adding the custom javascript to the page head.");
}
return scriptCollection;
}


I never recommend customizing directly the portal classes...that way it is a bit simpler if you need to upgrade the portal version: you custom code is not everywhere.



So instead of customizing directly MyPortalDP and GatewayHostedDP, create new custom classes that inherit from those 2...then make sure your custom classes are loaded properly in the related Activity Spaces (PlumtreeAS and GatewayAS)...



Hope that is helpful!

by Fabien Sanglier (noreply@blogger.com) at June 10, 2009 03:34 AM

June 03, 2009

Hauke Wesselmann

Configuring ALI Directory Service

No Gravatar

With the release of Aqualogic User Interaction 6.5 BEA introduced a new service - the ALI Directory Service. This neat little peace of software exposes your internal user database to the world LDAP v3-compatible (what I am trying to say: ALUI can act as a true LDAP server now).
This makes interacting with your own portlets more attractive and enables you to make the portal your primary user management instance (if you like the sound of it). But as usual, there is a problem: Some internal components, e.g. the notification service require this service to be configured and running, so making it work is not really optional.
This shouldn’t be a problem - just check the manual and go! Unfortunately, the complete BEA documentation division seems to have quitted before 6.5 was released - yes, there is no written or online documentation I could find to-date :-(

But setting up is not that hard…

Disclaimer:
This guide is created for helping out setting up the directory service. Although I have not yet experienced any difficulties, the following steps may break your portal system and/or database. I will not give any warranty nor take responsibility for it to work out properly. Do at your own risk!

First you need to modify your portal database (make a backup first, of course!). Move to the machin where you installed the directory service. Browse to <<BEA home>>\alui\aluidirectory\1.0\sql\ and chekc the README-file. In the subfolder for your chosen database, you will find four sql-scripts.

Screenshot showing a typical path to directory services database scripts

No need to mention all these scripts need to be executed on your database-machine(s) and against the portal database.
First, you will need to run create_tables.sql. This will create some tables required for mapping your user information in a LDAP-compatible structure. Next, run create_functions.sql in order to get all needed procedures into your dbms.
The last step is to map your users to the new schema by executing map_alidb_65.sql. This procedure may take some time, depending how much user information you have entered by now…
The fourth script drop_tables.sql can be run if you require to remove the directory service or the mapping tables. This will affect only the data required exclusively for usage with  the directory service.

Now you are nearly done. The final thing to do is to open the configuration manager - a web configuration helper also introduced with this release. Here, find the entry for the directory service and check your settings. You need to enter your database connectivity and credentials. Apply your settings and (re)start the service.

Screenshot of the Directory Service configuration tab

Tadaa! You can now test if all went well by connecting to the service. I used Softerra’s LDAP Browser to verify my installation, but you can use any LDAP Browser you like, of course ;-)
You need to connect to the service using
username: uid=administrator,ou=users,dc=bea,dc=com
passwort: <<your admin’s pass>>

Note:
All of these steps also apply to 6.5MP2 aka Oracle Webcenter Interaction 10.3.

by Hauke at June 03, 2009 08:19 PM

June 02, 2009

Bill Benac

Changing Ports: Reconfiguring Configuration Manager

Sometimes you just need to change ports. When I was a kid, it was the port of New York:

port-newyork-400.jpg

In search of love, I tried the port of Syndey (from which I supported a Plumtree portal system):

port-syndey-400.jpg

Later I found true love in the port of Seattle (from which I supported a BEA ALUI portal system):

port-seattle-400.jpg

And now the local port is San Francisco (from which I support an Oracle WCI portal system).

port-sanfran-400.jpg

And speaking of those portals and changing ports, sometimes you realize that your Configuration Manager service would suit you better if it ran on a different port. You can change the port by following these steps:

1. Remove the existing configuration manager service:

%PT_HOME%\configmgr\2.0\bin\configmgr.bat remove

2. Backup then edit %PT_HOME%\configmgr\2.0\settings\config\private.xml. Set the "EAS:httpsport" to the desired port.

3. Backup then edit %PT_HOME%\configmgr\2.0\settings\config\wrapper.conf. Edit the wrapper.ntservice.name and wrapper.ntservice.displayname to reflect the desired port.

4. Install the configuration manager service:

%PT_HOME%\configmgr\2.0\bin\configmgr.bat install

5. Start the configuration manager service.

Enjoy!



by bill at June 02, 2009 05:42 PM

May 31, 2009

Ross Brodbeck

EC2/Cloudwatch Gaming Results

As I mentioned in my previous post, I wanted to capture some real world info on hosting a game server in the cloud. The results were a rousing success. We had 5 or 6 people connected at various times, played some Deathmatch and Capture the Flag, and everyone had a ping of 40 or less the entire time. I didn’t notice any latency whatsoever and there were absolutely no packet loss or lag complaints throughout.

Cost

I haven’t broken down the numbers yet, but all told I started up an EC2 instance and hosted a game for 2 hours. I also attached an elastic IP for ease of use. That cost me less than $0.50. I’d say that’s a pretty good deal.

Usage

Below are the usage stats for network I/O and CPU usage. I gathered these using my simple Java application and created these no-frills charts in Microsoft Excel (all told, this took about 5 minutes to put together):

image

Figure 1 – Network I/O over a 2 hour F.E.A.R. game.

 

image

Figure 2 – CPU Usage over a 2 hour F.E.A.R. game.

Conclusion

This is a short and imperfect analysis, but overall I’d say the “small” EC2 instance could easily have handled a 16 person game, both from a load and network traffic standpoint, and it would have cost me a dollar or so to host for 2 hours. That seems like great bang for your buck if you’re looking to crank up a quick game and then move on to something else.

by hross at May 31, 2009 10:37 PM

May 27, 2009

Hauke Wesselmann

Collaboration 4.5 UI customizing

No Gravatar

In my first post here on my new blog, I will try to show how to adapt the looks of the overview page in ALUI/WebCenter Interaction Collaboration 4.5.

When we recently upgraded our portal-system from BEA ALUI 6.1MP1 to 6.5 (yes, we’re not WebCenter Interaction quite now), we also installed the current version of ALUI Collaboration (4.5).
There are lots of new features, most of them working well and making the product attractive. All but one: the refreshed overview page in application mode. BEA has integrated Dojo and implemented some nice features - drag’n'drop, inline-refresh and other dhtml/ajax-tricks. That does not sound annoying? The features aren’t the problem, though.
Our current stylesheets, which came from Collaboration 4.2 do not affect this page anymore! Sure, you could try to include the new templates, push all of it trough your cssmill, but that will not work either. So what can you do if your company’s colours are (accidently) not lightblue?

collaboration_defaultcolours

First of all, I modified the collab.war-file (making a backup of the file first, of course!). This step isn’t necessary, but it was easier to change the look of the header for me afterwards.  My little modification helped me avoiding unwanted changes in other sections of our portal.
Open your war-file using an archiving application like WinRAR. Navigate to the folder \layout\templates and open the file appViewLayout.jsp contained in this folder.
Change line 107

1.<div class=“collab”>

to

1.<div id=“collab”>

Now you can change all entries in the portal styles-section of your stylesheet and can adapt your own look’n'feel far easier. Here’s my version of the stylesheet:

01./* portal styles */
02.#collab a                   {color:#666666;}
03.#collab td                  {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
04.#collab .objectHeader       {color: #000000; font: bold 10px arial,helvetica,“sans-serif”; text-decoration: none; padding: 0px 0px 0px 5px;}
05.#collab .objectHeaderBg     {background-color: #c72820;}
06.#collab .objectContentBg    {background-color: #EDEDED;}
07.#collab .objectText         {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
08.  
09.#collab .listSortHeaderBg   {background-color: #666666;}
10.#collab .listSortHeader     {color: #fff; font: bold 10px arial,helvetica,“sans-serif”; text-decoration: none; padding: 0px 3px 0px 3px;}
11.#collab .listText           {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
12.#collab .listText A         {color: #003399; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
13.  
14.#collab .formTextboxText    {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
15.#collab .formBtnText        {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none; background-color: #efefef;} /* copy to clipboard */
16.#collab .formPulldownText   {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
17.#collab .formInputBoxText   {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;}
18.#collab .formEditorBtnText  {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none; background-color: #efefef;} /* close button with bluish background */
19.  
20.#collab .banTopbarBg        {background-color: #c72820; border-top: 1pt solid #777777;border-left: 1pt solid #777777;border-right: 1pt solid #777777;} /*used for collab tabs background*/
21.#collab .actionbarText      {color: #000000; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;} /*used for collab tabs text*/
22.#collab .banHeaderBg        {background-color: #a0261d;} /* used for displaying project name banner */
23.#collab .banHeader          {background-color: #a0261d; color: #ffffff; font: bold 14px arial,helvetica,“sans-serif”; margin-top: 0px; margin-bottom: 0px;} /* used for project name title */
24.  
25.#collab .banText            {color: #FFFFFF; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;} /*search text */
26.#collab .banText A          {color: #fff; font: 10px arial,helvetica,“sans-serif”; text-decoration: none;} /*links within tabs and help link */

Let’s get rid of the blue titlebars now :-)
This problem is solved fairly simple. Insert the following lines in your current stylesheet (and/or your cssmill-template). These lines show my adaption in the stylesheet, making the titlebars appear in light red.

1..modulecontent  div .dojoModuleTitle {
2.    color: #fff;
3.    padding:1px 2px;
4.    vertical-align: center;
5.    font: bold 10px verdana,arial,helvetica,“sans-serif”;
6.    height:20px;
7.    background-color: #c72820;}

That’s it! Save, reload your appview-window and enjoy ;-)

by Hauke at May 27, 2009 04:08 PM

May 21, 2009

Ross Brodbeck

Update Publisher Publishing Targets

I have recently been working on a utility for porting ALUI databases from a production environment to a development environment. Fabien Sanglier started this effort, and I hope to have some code to contribute to his ALUI toolbox project very soon.

In the meantime, however, I have been banging my head against the pain that is migrating Publish and Preview target URL’s in Publisher. These URL’s are stored in a binary BLOB in the Publisher database, and are actually serialized Java classes, making them extremely difficult to update (especially when you don’t have access to the original Publisher source code).

My original plan was to wrap all of this stuff into one “uber-utility” and then blog about it. Recently, though, I saw this post on the Oracle Webcenter Interaction discussion forums: http://forums.oracle.com/forums/thread.jspa?threadID=900736&tstart=0 and it made me think I should probably post the code for migrating Publishing Targets, for the benefit of the sanity of the community at large.

Here is a link to a jar file which will update Publisher publish targets. If you crack the jar file with a zip editor, you will be able to update the configuration.properties file in the root directory to suit your needs.

I took the liberty of including the Publisher classes in my own jar, making it simpler to run from a command line. To run it, you will only need to download the correct jdbc driver for your database:

Oracle JDBC Driver

SQL Server JDBC Driver

Next, simply execute it from a java command line with the driver in your classpath, like so:

java -cp updatepublishtargets.jar;ojdbc14.jar net.hross.content.UpdatePublishTargets

Note that the utility is in debug mode by default, so nothing will happen to your Publisher database until you set debug to false in the configuration, although now is probably a good time to let you know that I provide no warranties of any kind with this code.

In order to build and run the source, you will need the content.jar and dom4j.jar found in the WEB-INF/lib directory of your ptcs.war. Here is the relevant source code, in case you are looking to build your own version of the utility (source is also in the jar):

package net.hross.content;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import net.hross.utility.Configuration;

import com.plumtree.content.data.AttributeKey;
import com.plumtree.content.data.impl.RdbiPublishingTarget;

public class UpdatePublishTargets {

    public static void main(String[] args) {
        Connection connection = Configuration.getConnection();

        if (null == connection) {
            System.out.println("Unable to connect to database. Exiting.");
        }

        int directoryId = Integer.parseInt(Configuration
                .getString(Configuration.CONFIG_DIRECTORY_ID));
        boolean debug = Boolean.parseBoolean(Configuration
                .getString(Configuration.CONFIG_DEBUG_MODE));
        String newPublishTarget = Configuration
                .getString(Configuration.CONFIG_PUBLISH_TARGET);

        System.out.println("Updating publish targets for directory ID: "
                + directoryId);

        System.out.println();
        if (debug) {
            System.out.println("** DEBUG MODE ON ** Nothing will be updated.");
        } else {
            System.out.println("** DEBUG MODE OFF ** This is happening for real.");
        }
        System.out.println();

        updatePublishTarget(connection, directoryId, newPublishTarget, debug);
    }

    /***
     * Update the publishing target for a specified directory ID (-1 for all
     * items).
     * 
     * @param connection
     *            Publisher database connection.
     * @param directoryId
     *            Directory ID to update. -1 for all items.
     * @param newPublishTarget
     *            - New publishing target.
     * @param debug
     *            - if true, no replace will be made, data will just be output.
     */
    public static void updatePublishTarget(Connection connection,
            int directoryId, String newPublishTarget, boolean debug) {

        // create a statement to query the directory id
        try {

            // create prepared statement for directory query
            PreparedStatement psDirectory = null;
            if (directoryId > 0) {
                psDirectory = connection
                        .prepareStatement("SELECT * FROM PCSDIRECTORY WHERE ITEMTYPE=0 AND DIRECTORYID=?");
                psDirectory.setInt(1, directoryId);
            } else {
                psDirectory = connection
                        .prepareStatement("SELECT * FROM PCSDIRECTORY WHERE ITEMTYPE=0");
            }
            ResultSet rs = psDirectory.executeQuery();

            // loop through any rows we need to check
            while (rs.next()) {

                // get basic info about the object
                String itemName = rs.getString("ITEMNAME");
                int size = rs.getInt("DATASIZE");
                
                // reset directory ID in case it was generic
                directoryId = rs.getInt("DIRECTORYID");

                // get binary input stream
                InputStream input = rs.getBinaryStream("DATABYTES");

                // if there's actually some settings, let's check them
                if ((null != input) && (0 != size)) {

                    // generic catch statement for problems with this item
                    try {
                        byte[] buffer = new byte[size];
                        input.read(buffer);

                        // load the hash map from the database
                        Map map = (HashMap) deserialize(buffer);

                        // loop through the keys in the hash map
                        Iterator keys = map.keySet().iterator();
                        while (keys.hasNext()) {
                            Object key = keys.next();

                            // this should probably always be true
                            if (key.getClass().equals(AttributeKey.class)) {
                                AttributeKey akey = (AttributeKey) key;

                                // if we found a publishing target...
                                if (akey.getKeyString().equals(
                                        "PUBLISHING_TARGET")) {
                                    System.out.println();
                                    System.out.println("--------------------");
                                    System.out
                                            .println("Updating publishing target for:");
                                    System.out.println(directoryId + " - "
                                            + itemName);

                                    // get the publishing target info
                                    RdbiPublishingTarget val = (RdbiPublishingTarget) map
                                            .get(key);
                                    String publishTarget = val
                                            .getPublishDetail()
                                            .getTargetLocation();
                                    String publishBrowser = val
                                            .getPublishDetail()
                                            .getBrowserLocation();
                                    String previewTarget = val
                                            .getPreviewDetail()
                                            .getTargetLocation();
                                    String previewBrowser = val
                                            .getPreviewDetail()
                                            .getBrowserLocation();
                                    String ftpUser = val.getPublishDetail()
                                            .getUsername();
                                    String ftpPassword = val.getPublishDetail()
                                            .getPassword();

                                    System.out
                                            .println("Publish  browser location: "
                                                    + publishBrowser);
                                    System.out.println("Preview target: "
                                            + previewTarget);
                                    System.out
                                            .println("Preview browser location: "
                                                    + previewBrowser);
                                    System.out.println("FTP user: " + ftpUser);
                                    System.out.println("FTP password: "
                                            + ftpPassword);
                                    System.out.println("Old publish target: "
                                            + publishTarget);
                                    System.out.println("New publish target: "
                                            + newPublishTarget);

                                    // if we are doing this for real, update
                                    // values
                                    if (!debug) {
                                        val.setTargetValues(newPublishTarget,
                                                publishBrowser, previewTarget,
                                                previewBrowser, ftpUser,
                                                ftpPassword);

                                        map.put(key, val);

                                        // update the directory
                                        serializeToDirectory(connection,
                                                directoryId, map);
                                        System.out.println("Update successful.");
                                    }
                                    System.out.println("--------------------");
                                    System.out.println();
                                }
                            }
                        }

                        // clean up
                        input.close();
                    } catch (IOException ex) {
                        System.out.println("Something bad happened.");
                        ex.printStackTrace();
                    }
                } // if null
            } // while next rs
        } catch (SQLException ex) {
            System.out.println("Something bad happened.");
            ex.printStackTrace();
        }

        System.out.println("Procedure successfully completed.");
    }

    private static Object deserialize(byte bytes[]) {
        try {
            ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
            ObjectInputStream objectStream = new ObjectInputStream(byteStream);
            return objectStream.readObject();
        } catch (Exception ex) {
            return null;
        }
    }

    private static void serializeToDirectory(Connection conn, int directoryId,
            Object obj) throws IOException, SQLException {
        byte bytes[] = getBytes(obj);
        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);

        PreparedStatement ps = conn
                .prepareStatement("UPDATE PCSDIRECTORY SET DATASIZE=?, DATABYTES=? WHERE DIRECTORYID=?");
        ps.setInt(1, bytes.length);
        ps.setBinaryStream(2, byteStream, bytes.length);
        ps.setInt(3, directoryId);
        ps.execute();
        conn.commit();
    }

    public static byte[] getBytes(Object obj) throws java.io.IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);
        oos.flush();
        oos.close();
        bos.close();
        byte[] data = bos.toByteArray();
        return data;
    }
}

by hross at May 21, 2009 12:09 PM

May 20, 2009

Ross Brodbeck

Monitoring Performance with Amazon CloudWatch

It is rare that I am on the bleeding edge of technology. Normally, I don’t think its worth the time and effort necessary to learn something brand new unless it has been at least somewhat widely adopted and accepted by the community at large.

Oddly enough, my blog post about running a game server on EC2 turned out to be perfectly timed, as Amazon launched its new CloudWatch, Elastic Scaling and Load Balancing services on Sunday. And since, as I discussed earlier, I have been looking at ways to monitor the usage of my EC2 game server, I somehow find myself on the bleeding edge of the cloud.

Why CloudWatch?

As I discussed in my previous post, setting up monitoring on an EC2 instance wasn’t that hard to do. However, it did come with some drawbacks:

  • Maintenance – Although it can be fun to install new software and learn its in’s and out’s, the actual task of upgrading that software, maintaining it, patching it, watching it for security risks, etc, etc is a major pain in the rear end. CloudWatch solves this problem by providing a simple service for retrieving performance data, no maintenance or special setup required.
  • Granularity – As I discovered with munin, there are limitations to the frequency with which you can store performance data, not to mention the storage requirements for vast quantities of it. Again, this is hidden from us in the case of CloudWatch.
  • Performance – Last but certainly not least, monitoring something usually incurs a performance hit. In my previous article I was sampling data on the same host I was tracking statistics from. The very act of collecting performance data could cause that data to be skewed. Since CloudWatch abstracts this away from individual instances, this is no longer a problem.

Getting Started With CloudWatch

There are quite a few resources available to get you started with CloudWatch. I recommend taking a look at the javascript scratch pad and the other various developer libraries already available (more on this later).

If you really want to get down to the nitty gritty, you should start with the CloudWatch command line interface (CLI). Here are some simple steps to get you started:

  1. Download the EC2 API Tools first (you’ll need them to set up monitoring). Check out the Getting Started Guide for instructions on extracting the tools and setting up the proper environment variables.
  2. Download the CloudWatch API Tools. Check out the included readme for details on environment variable setup.
  3. Start up an EC2 instance like you normally would (see my previous post).
  4. Enable monitoring on your running instance using the EC2 API Tools command: ec2-monitor-instances <instanceId>.
  5. Take a look at the CloudWatch Getting Started Guide for details on the available monitoring parameters, etc.
  6. Run the CloudWatch command mon-get-stats to get some statistics from your running instance (mon-get-stats –help should give you some examples).

Here are a few things to keep in mind when running the command line utility:

  • I normally output data to a CSV file so I can create fancy graphs in Excel. Here is an example command (Windows) that delimits stats by comma and outputs to a CSV file:
    mon-get-stats CPUUtilization --start-time 2009-05-19T21:00:00
     --end-time 2009-05-19T22:00:00 --period 60 --statistics Average 
    --namespace AWS/EC2 --delimiter "," 
    --dimensions "InstanceId=i-2bb5cc42" > stats.csv
  • Timestamps – As per the forums, input timestamps are in ISO-8601 format with the default timezone UTC (Eastern Standard Time + 4 hours). Output timestamps are in UTC and cannot be changed (so start thinking in Greenwich Mean Time).
  • Virtually as soon as monitoring is enabled, statistics are retrieved from your instances. Data is available up to a per-minute frequency and is stored for two weeks.

Writing a Simple Java Monitoring Utility

As much fun as I was having trying to parse and decipher various command line inputs, I was somewhat disappointed in the output. For one thing, there was the time formatting problem. For another, only one set of statistics (CPU utilization, network I/O, etc) were available at one time.

I am not one to do more work than I need to, so instead of setting off to invent an uber-utility for aggregating data, I simply downloaded the Java library for CloudWatch and hacked up some of the sample code until I had a very basic utility for downloading and aggregating the data I wanted. I present it below in case someone finds it useful:

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

import com.amazonaws.cloudwatch.AmazonCloudWatch;
import com.amazonaws.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.cloudwatch.AmazonCloudWatchException;
import com.amazonaws.cloudwatch.model.Datapoint;
import com.amazonaws.cloudwatch.model.GetMetricStatisticsRequest;
import com.amazonaws.cloudwatch.model.GetMetricStatisticsResponse;
import com.amazonaws.cloudwatch.model.GetMetricStatisticsResult;

public class GrabStats {

    public static void main(String[] args) {
        
        String fileName = "C:\\stats.csv";

        String startTime = "2009-05-19T20:00:00";
        String endTime = "2009-05-20T00:00:00";
        
        String[] statList = { "CPUUtilization","NetworkIn","NetworkOut" }; //(%, bytes, bytes)
        
        HashMap<String, HashMap<String, Double>> map = new HashMap<String, HashMap<String, Double>>();
        
        // grab stats for each stat value
        for (int i = 0; i < statList.length; i++) {
            HashMap<String, Double> stats = getStatistics(startTime, endTime, statList[i]);
            map.put(statList[i], stats);
        }
        
        // write to disk
        try {
            FileWriter fw = new FileWriter(fileName);
            
            // write the header
            fw.write("Date");
            for (int i = 0; i < statList.length; i++) {
                fw.write(",");
                fw.write(statList[i]);
            }
            fw.write("\n");
            
            // get a date iterator from our first statistic
            Iterator<String> dateIterator = map.get(statList[0]).keySet().iterator();

            while(dateIterator.hasNext()) {
                String date = dateIterator.next();
                fw.write(date);
                
                // get values for each stat at this date
                for (int i = 0; i < statList.length; i++) {
                    Double value = map.get(statList[i]).get(date);
                    fw.write(",");
                    fw.write(value.toString());
                }
                
                fw.write("\n");
            }
            
            fw.close();
        } catch (IOException ex) {
            // error storing data
            System.out.print("Error writing file: " + fileName);
        }

    }

    // define the cloudwatch service (should be a singleton)
    private static final String _accessKeyId = "<insert key here>";
    private static final String _secretAccessKey = "<insert access key here>";
    private static AmazonCloudWatch _service = new AmazonCloudWatchClient(
            _accessKeyId, _secretAccessKey);

    public static HashMap<String, Double> getStatistics(String startTime,
            String endTime, String statName) {
        HashMap<String, Double> map = new HashMap<String, Double>();

        // build the request with some defaults
        GetMetricStatisticsRequest request = new GetMetricStatisticsRequest();
        ArrayList<String> stats = new ArrayList<String>();
        stats.add("Average");
        request.setStartTime(startTime);
        request.setEndTime(endTime);
        request.setPeriod(60); // statistics every minute
        request.setMeasureName(statName);
        request.setNamespace("AWS/EC2");
        request.setStatistics(stats);

        try {

            GetMetricStatisticsResponse response = _service
                    .getMetricStatistics(request);

            if (response.isSetGetMetricStatisticsResult()) {
                GetMetricStatisticsResult getMetricStatisticsResult = response
                        .getGetMetricStatisticsResult();
                java.util.List<Datapoint> datapointsList = getMetricStatisticsResult
                        .getDatapoints();
                for (Datapoint datapoints : datapointsList) {
                    map.put(datapoints.getTimestamp(), datapoints.getAverage());
                }
            }

        } catch (AmazonCloudWatchException ex) {

            System.out.println("Caught Exception: " + ex.getMessage());
            System.out.println("Response Status Code: " + ex.getStatusCode());
            System.out.println("Error Code: " + ex.getErrorCode());
            System.out.println("Error Type: " + ex.getErrorType());
            System.out.println("Request ID: " + ex.getRequestId());
            System.out.print("XML: " + ex.getXML());
        }

        return map;
    }

}

Conclusion

The CloudWatch tools and utilities are nothing less than I’d expect from Amazon. Everything worked as expected, the documentation was well put together and there were no real surprises with the API. Overall, I am very satisfied with the finished product of my meager efforts.

There are, of course, a few shortcomings:

  1. It would be nice to have more statistics available (memory usage being the main one I’m thinking of). Having the ability to define and collect your own statistics via an API would be even better. Since the API already has a flexible way of defining statistic and type, I have to assume this is coming.
  2. Output visualization is certainly lacking. It would be great to see someone hack a Google Chart generator into the javascript scratch pad (given my lack of copious amounts of free time, this person won’t be me).
  3. Adding some statistic collection and enablement to ElasticFox would certainly make things easier to set up and administer.

I have to assume these drawbacks will be addressed in future updates, as they have been in the past. I am willing to accept them as the price to pay for being on the bleeding edge of the cloud.

by hross at May 20, 2009 12:13 AM

May 19, 2009

Function1

Oh ALBPM, why you gotta go and load images like 'dat?

Howdy all.  Hope the nice weather is finding happy, healthy, and allergy free. As we were doing spring ALBPM house-keeping with a client the other day, we stumbled upon a bit of a problem with the way that BPM loads some images.  This led us to go digging into how the ALBPM->Portal integration works.  And anytime I get a reason to dig into the guts of software, it usually ends up as a blog post....hope you enjoy.

Cliff Notes if you don't want to ready my wordy explanation

  • The BPM->Portal integration makes use of the old Plumtree OpenControls libraries
  • Out of the box, the OpenControls libraries in use with the BPM->Portal integration are configured to go all the way back to the BPM container to load static files (images, js, css, etc) on every request to the portlet-ized BPM workspace.
  • There are a bunch of static files that get loaded every time you hit the BPM workspace.  The net result is that the BPM portlets are slower to render than they should be.
  • There's a servlet filter configured in the BPM workspace web.xml to cache static files on the client, but it doesn't seem to work.
  • You can force Open Controls to load images from a webserver of your choice (i.e. the portal image server where you presumably having caching set up) by adding the following entry to $BPM_HOME/enterprise/webapps/workspace/WEB-INF/web.xml
<context-param>
<param-name>com.bea.opencontrols.IMAGESERVER_URI</param-name>
<param-value>http://your.imageserver.url/bpmContext</param-value>
</context-param>

  • You'll also need to copy the following directory into your bpmContext folder of your imageserver:
$BPM_HOME/enterprise/webapps/workspace/opencontrols/plumtree

  • Bounce the BPM workspace after making the changes above and you should see a marked improvement in workspace performance

For the masochists amongst you, the long-winded explanation

First, a "Did you Know" on the BPM->Portal integration

  • Did you know that the BPM->Portal integration was originally written by Plumtree way back when as a way to bolt Fuego BPM into the Plumtree Portal?  This original codebase is still more or less intact, and is still used for the integration today.
  • Did you know that the BPM->Portal integration is built in Java and uses Java Server Faces (JSF)?
  • Did you know that the BPM->Portal integration also makes use of the somewhat dated Plumtree  OpenControls libraries?
  • Did you know that the code for the standalone BPM web-based client is a lot cleaner than the BPM->Portal integration?
  • Did you know that there are servlet filters configured on the BPM Workspace that should do neato stuff like caching and compression auto-magically?  
OK, so the whole "Did you know" thing is a bit of stretch, and really just a vehicle to give you background on the issue we were researching.  So....

The Issue We Were Researching
It turns out that, out of the box, the portlet-based BPM workspace doesn't handle some images too smartly.  Specifically, on every UI request, there are a bunch of static images that get reloaded all the way from the embedded Tomcat server that runs the BPM workspace.  Now, everybody and their brother knows that it's just good common sense to cache images, so what gives?  Welp, let's start at the very beginning and see.  After users reported slow-loading images in the BPM workspace, we used one of the best tools known to mankind to take a look all the HTTP requests being made on page load:


bpm_broken.png

As the lovely highlighting above points out, some images in the workspace are getting loaded from the portal imageserver, and are being appropriately cached in the client browser.  We know they're being cached by that handy-dandy 304 HTTP status code.  But what's up with all these requests for images and javascript files with HTTP status code 200 (i.e. going back to the server to successfully load a static file on every page invocation)?  And why do all those URLs end with "WebResource.resource?r=foo"?  Blasphemy!  Time to do some poking around and set things straight.

How we researched the issue
This is the section of the blog post where I take you deep inside the black art of tearing apart a web application and making educated guesses to arrive at a testable hypothesis.  Strap in for a non-stop thrill ride.

Anytime I'm faced with a black box type problem (i.e. there's something going wrong with a piece of commercial software to which I don't have the source code), I more or less go through the following checklist:

  • Is this something I've seen before?
  • Is this something that somebody else has seen before?
  • Is it going to be more painful to open a support ticket on this than to just figure it out myself?
  • What do I know about the inputs and outputs of the black box?
  • What do I know about how the application is built?
  • How can I tear the application apart to learn something useful?

In this case, we were looking at a problem that we'd never noticed before, nor could we figure out a way to ask the interwebs if anybody else had seen it before.  I was pretty sure that going through the process of working a support ticket was going to be more painful that just fixing the problem (besides, what fun is working with support when you can break into the guts of a system?), so I asked myself, "What do I know about the inputs and outputs of the black box"?

  • Input -> HTTP Request to a web page that loads multiple static files and some dynamic content
  • Desired output -> Cached static content
  • Actual output -> Non-cached static content 

Then I thought about what I know about how the application is built:

  • Java
  • Web application
  • JSF

Finally, how can I tear the application apart to learn something useful:

  • Look at the inputs (i.e. look at the URLs being requested)
  • Look at the web.xml and other configuration files of the application to see how everything is glued together
  • Decompile the source to see what the hell is going on

So off we went.  The starting point in our maze was to take a good look at the problem image requests (Warning: I'm going to get all stream of consciousness on you here...welcome to my troubled world)

https://portal.foo.com/gateway_noise/bpm_portlet_server:123/workspace/jsf/menu/WebResource.resource?r=/a/path/to/an/image.gif

Let's strip that down to look at the real interesting stuff:

bpm_portlet_server:123/workspace/jsf/menu/WebResource?r=/a/path/to/an/image.gif</