Thursday Feb 12, 2009

Web Space Server Basic Intro

As part of the big GlassFish Web Space Server launch, I have created a brief (7min30sec) intro demonstrating downloading, extracting, starting up, and looking at the packaging and filesystem layout of GlassFish Web Space Server.  Also I briefly look at Update Center and how Web Space Server is managed through it.  It's a Quicktime .mov file.  If it doesn't appear below, you try the mediacast page that hosts it.



Tuesday Jun 24, 2008

Friends don't let friends...


Use nicks from pesudo-tech movies... even if Angelina is in them!




Thursday May 29, 2008

Portal Server 7.2 Released

Today (ok! It was really last week but the marketing page finally got updated) we released the next version of Portal Server, version 7.2 [Download].  Based on the open source OpenPortal code & community, this version brings a bunch of new features.  The Release Notes details the features.  I even updated the Wikipedia entry :) But more importantly, this is the first of the modern portal versions that has been released independently, meaning not bundled with any larger offering, resulting in smaller downloads, more modularity, and less overall baggage that was introduced with previous bundles.


A general picture of the components of Portal 7.2 and how they are structured is below: 




Some highlights of this release include:


If you get a chance, please check the latest release out (see free download link above).



Wednesday May 07, 2008

Sun/Liferay Initiative

Today @ JavaOne Sun and Liferay announced a dual initiative to develop next generation web technologies (details and FAQ).  That's the marketing tagline.  I just wanted to expand a bit on what this means, the benefits, and what's next.

Sun already produces a free, open source portal offering through the OpenPortal project.  The next release (7.2) is slated for later this month.  For existing and new enterprise customers, this release contains new features and capabilities such as theme tooling, delegated administration, JSR 286 (Portlet 2.0) support, Google Gadgets integration, and integration with human workflow engines via SAW.

Liferay also produces a free, open source portal offering - Liferay Portal.   It also has a number of great features such as integrated content management, a host of collaboration capabilities (blogs, wikis, social networking), and support for publishing portlets to MySpace and Facebook. 

Sun's new offering will continue our tradition of providing a high-quality, enterprise-ready application platform.  What's new you ask?  Well, here are a few points to make regarding what's changing in the Sun stuff:

  • Incorporation of Liferay Portal Server as the base portal platform which provides features such as the desktop, themeing, role-based content delivery, communities, as well as a number of features contributed by Sun such as Portlet Container 2.0, WSRP, and OpenSSO integration and integrated into the Liferay community.
  • New features to take advantage of modern web-based APIs for doing multi-language mashups, including developer tooling (e.g. in NetBeans) to vastly simplify widget and mashup creation.
  • Syndication of widgets and mashups via REST

Besides features, the Sun and Liferay communities are partnering to exchange engineering and architectural designs for mutual benefit, contributing code back and forth, and generally fostering a communal effort that is greater than the sum of its parts.  Visit the details and FAQ page for more information, and download links so you can try out the working demo, which is based on Liferay, GlassFish v3, and MySQL.

Also, check out this screencast (also included on the CD/memstick mentioned below) showing multi-language inter-widget communication using this new platform.

Also, if you are attending JavaOne, be sure to visit the Sun GlassFish and Portal booths, and Liferay booth and grab a memory stick or CD and tee shirt, and say hi!

 

Monday May 05, 2008

Presentation @ CommunityOne

It's been a while since blogging!  A lot has changed since late 2007 :)  I have moved to the Portal group, working on Sun's next generation portal technology, providing lightweight widget aggregation and presentation, multi-language widget communication, and a host of other enterprise features.

Today I am at CommunityOne, Sun's free-to-attend conference the day before JavaOne.  I did a presentation at 11am on "Breaking Down Barriers: Widgets, Mashups, and WOA for the Enterprise" where I did a general overview of the technology used to create "Web 2.0" widgets using web APIs made available by the likes of Google, Amazon, Facebook, Flikr, and others.  It was the first time doing this presentation, so there were the usual flubs, "uh's" and uncomfortable pauses but generally it went well, with some excellent post-preso questions from the audience.  I'll post the video as soon as it hits the CommunityOne/JavaOne site.  Thanks to all those who attended.  I also showed a demo of Project WebSynergy, the moniker we've attached to the future of middleware widget aggregation platforms.  Thanks to all who attended. There will be additional press releases on Wednesday associated with this new project.  More to follow...

Sunday Oct 21, 2007

Geertjan's interview with openInstaller IDE Author Vadiraj Deshpande

Vadiraj Deshpande, NetBeans extraordinaire and openInstaller guru was recently interviewed by Geertjan, the famous NetBeans writer and blogger, and had some very nice things to say about developing the openInstaller IDE using NetBeans!

A screenshot can be found below showing the simple "generate installer" button that integrates into NetBeans IDE when the plugin is installed.  In addition, there is a standalone IDE that can be used to create more complex installers.

 


 



Thursday Jul 26, 2007

Previewing diffs with Mercurial and tkdiff

Just a quickie today.  I am merging some work I did from a really old Mercurial repository, into a much more recent repository.  Because of the big changes that have gone in, I am hand-merging.  I needed a way to easily browse diffs from the old work, so that I can manually cut/paste as needed into the new repository.  Here is a short script I wrote which uses Zenity to show a list of files with diffs, and when I double-click on the file, it brings up the diffs in tkdiff.


#!/bin/sh
while [ 1 ] ; do
FILELIST=`hg status -n`
FILE=`zenity --list --column="File to show diffs for" $FILELIST`
if [ -z "$FILE" ] ; then
exit 0
fi
hg cat $FILE > /tmp/hg.orig.$$
tkdiff /tmp/hg.orig.$$ `hg root`/$FILE
rm -f /tmp/hg.orig.$$
done

 

 

Wednesday Jul 25, 2007

Merging with tkdiff and Mercurial

I first encountered Mercurial last year when my project (openInstaller) decided to switch from TeamWare to Mercurial because everyone else was doing it :)  I had many years of TeamWare experience and was reluctant to switch.  Well, I got over it, and now I see why everyone was all fired up.  It's a pretty sweet system.  But that's not the point of this article.

Merging sucks

I personally hate to merge.  I avoid it whenever possible, because in a complex merge, you'll usually make a mistake or two.  I've also done my share of silent mismerges.  Usually, if I have a large-ish change and have to merge it with another large-ish change, I'll do it manually (e.g. take the parent repository, and manually re-introduce my changes into the files in conflict by hand).  Mercurial and TeamWare both support the concept of merging, but mercurial does it better under the covers (heh, sounds like a great bumper sticker).  The problem is, the stock version of Mercurial on my Solaris Express (and probably openSolaris) doesn't find any GUI utilities like tkdiff or kdiff3 because they aren't installed on the stock version of the OS.

If I install tkdiff, things get better, but I'm faced with a ghastly set of default color choices:

I am blinded by black-on-white (or anything-on-white for that matter).  I prefer the "blackout windows" color themes, where the majority of your screen's pixels are black (or close to black) most of the time.  Easier on my eyes, anyway.  So, using this .tkdiffrc:


# This file was generated by TkDiff 4.1.3
# Mon Mar 26 12:14:03 EDT 2007

set prefsFileVersion {4.1.3}

# Automatically center current diff region
define autocenter {1}

# Automatically select the nearest diff region while scrolling
define autoselect {0}

# Tag options for characters in line view
define bytetag {-background blue -foreground white}

# Tag options for changed diff region
define chgtag {-background blue}

# Color change bars to match the diff map
define colorcbs {1}

# Tag options for the current diff region
define currtag {-background #333300}

# Tag options for deleted diff region
define deltag {-background #660000 -font {Courier -12 bold}}

# diff command
define diffcmd {diff}

# Tag options for diff regions
define difftag {-background #222222}

# Program for editing files
define editor {}

# Windows-style toolbar buttons
define fancyButtons {0}

# Text window size
define geometry {80x30}

# Ignore blanks when diffing
define ignoreblanks {1}

# Ignore blanks option
define ignoreblanksopt {-w}

# Tag options for diff region inline differences
define inlinetag {-background DodgerBlue -font {Courier -12 bold}}

# Tag options for inserted diff region
define instag {-foreground green -font {Courier -12 bold}}

# Tag options for overlap diff region
define overlaptag {-background yellow}

# Show change bars
define showcbs {1}

# Show inline diffs (byte comparisons)
define showinline1 {0}

# Show inline diffs (recursive matching algorithm)
define showinline2 {0}

# Show current line comparison window
define showlineview {1}

# Show line numbers
define showln {1}

# Show graphical map of diffs
define showmap {1}

# Synchronize scrollbars
define syncscroll {1}

# Tab stops
define tabstops {8}

# Highlight change bars
define tagcbs {0}

# Highlight line numbers
define tagln {1}

# Highlight file contents
define tagtext {1}

# Text widget options
define textopt {-background black -foreground gray -font {Courier -14} -wrap non
e}

# Directory for scratch files
define tmpdir {/tmp}

# Use icons instead of labels in the toolbar
define toolbarIcons {1

 I get a nice-looking mostly-black window.  Additions are represented with green, changes with blue, and deletions with red.

 

To install tkdiff on recent versions of Solaris, I had to:

1. Download it, extract it, and ensure that the 'tkdiff' binary was on my $PATH.

2. Solaris, for some bizarre does not include wish (The Tk windowing shell which tkdiff requires).  Oh, my mistake, it does include it, but the command is called wish8.3!  I brought this up earlier, but no satisfactory answer has come my way.  So, I simply created a symlink:


[jhf@spirit]:bin$> pwd
/home/jhf/bin

[jhf@spirit]:bin$> ls -l wish
lrwxrwxrwx 1 jhf other 20 Nov 9 2006 wish -> /usr/sfw/bin/wish8.3


Problem solved!
 

 

Monday Jul 23, 2007

OpenInstaller: Internationalization for Serviceability

If you have ever written internationalized programs for computers, you have invariably had to write i18n'd strings like

CANT_FIND_FILE=Cannot find file {0} because {1}
FILE_IS_NOT_READABLE=The file {0} is not readable. 

This could be a typical message seen in a UI (e.g. a popup error), or in a log file, or any other place where a user may encounter such an error.  Pretty straightforward, right?  Well, not when you think about how this string may be localized by a translation team in a different locale or language than you know.  In order to understand the problems that are faced here, you need to think about how computer programs of all kinds are designed such that they can easily be used by speakers and denizens of other locales, where the spoken and written language is different, the punctuations and symbols for things like date/time separators, currency symbols, and the like are different. 

How does a program get designed to be internationalized?

Typically the program's executable content is separated from the content that needs to be localized later (like user interface strings, images, audio, etc).  This allows the base program to be produced and re-produced at will, without depending on the translations of localizeable content to be available.  The to-be-localized content is typically kept in separate files which are produced along with the base content.  The to-be-localized content is then sent to one or more entities which perform the localization by translating the to-be-localized content.  This is typically done by giving a human being who is familiar with both locales (e.g. a person who is fluent in Japanese and American English, and is also familiar with customs in both locales).  This person's job is to translate between languages/locales.  They are given a giant list of strings/images/audio files, and produce an equally giant list as a result.  Each individual item is considered independent of others.

The problems: Context

So, when the French translator is faced with

CANT_FIND_FILE=Cannot find file {0} because {1}

They may translate this as

Impossible de trouver le fichier {0} car {1}

The problem is that the word "because" is stuck in there between two contextual items, but the translator has no idea what the content of {0} and {1} are (or will be).  For some (most) languages, the phrase is going to read wrong to a native speaker, when the phrase is re-constructed. If the "because" part was "out of memory", translated to "capacite memoire insuffisante", the the final phrase a French-speaking user would see is "Impossible de trouver le fichier /tmp/foo.txt car capacite memoire insuffisante" which is improper French.  A French-speaking person could figure it out, but it makes your application a little childish.  It gets even worse in Asian languages.

Taking this to an extreme, what if someone thought they were clever, and produced this in their to-be-localized file:

A=a
THE=the 
BAD=bad
EXAMPLE=example
TO=to
FOLLOW=follow

The coder was thinking "If I can get these 5 words to be translated I can use them over and over again and only require 5 actual strings to be localized, thereby saving money and complexity!" (typically, localization costs money on a per-word basis).  With these 5 words, one could produce any number of phrases in the program:

1. {A} {BAD} {EXAMPLE} {TO} {FOLLOW}
2. {FOLLOW} {A} {BAD} {EXAMPLE}

The French translator is going to translate the 5 words to:

{Un} {Mauvais} {Example} {A} {suivre}

Now, when the program is run in the fr (French) locale, when the {A} {BAD} {EXAMPLE} {TO} {FOLLOW} string is needed, the user is going to see "Un mauvais example a suivre"  Doesn't make much sense to a French-speaking person. This is an extreme example, but illustrates the problem of "context"

Dynamic substitution

Most applications that deal with Strings (like the above)  store the translations in a file that has a bunch of key/value pairs.  During execution, when a string needs to be shown, a lookup is performed on that table, to find the translation of a particular string for a particular locale/language.  The key used to perform the lookup is specified in the program.  e.g. in Java, to create a button, one might put:

JButton b = new JButton( "SOME_KEY" ) ;

The SOME_KEY is used to lookup the string to show to the user. 

A common error is including dynamic values in the key to be used in a lookup.  For example, in Unix shell script, one might use the gettext utility in this way:

echo `${GETTEXT} "${JAVA_HOME} must be the root directory of a valid JVM installation"`

See the problem?  The key used to look up the value in the translations will contain a dynamic pathname, based on the user's local system.  This key will obviously never be found in the translation table, because the translation table only contains ONE entry for this message (which, incidentally, will never actually be found, as the value of ${JAVA_HOME} at the time the translation table was created was probably "" (empty string)). 

The solution here is to remove the dynamic stuff from the string.  For example:

printf "`${GETTEXT} %s must be the root directory of a valid JVM installation`" ${JAVA_HOME}

Better still, to eliminate the problems of context (as explained above), one might:

 

printf "`${GETTEXT} Invalid JVM installation directory.` `${GETTEXT} directory`=${JAVA_HOME}" 

The solution is to completely avoid doing parameterized substitution in error messages, or any other message that needs to be localized.  This avoids the problems of lack of context and dynamic substitution illustrated above.  For example, instead of

FILE_NOT_FOUND=The file {0} could not be found because {1}.

This is instead written as:

FILE_NOT_FOUND=The specified file could not be found.

The "because" part (the reason the file could not be found) is not included in the original message.  Instead, it is associated with the error using a context object which is attached to the error message and optionally shown to the user when the final string is constructed for display (or logging).  The context items are shown with the error message, but not as part of the message.  They are typically shown after the message.  For example:

The specified file could not be found.  File=c:\\temp\\foo.txt Reason=Out Of Memory

Again, not all parameterized messages suffer from context problems.  However, as a best practice it results in more serviceable error messages and logs, especially when being serviced by personnel who aren't as fluent in a particular language or locale as a native.

openInstaller

Internally, openInstaller uses the org.openinstaller.util.EnhancedException class as a superclass for all project-specific exceptions thrown.  This class has the ability to attach one or more contexts.  For example:

 throw new EnhancedException("FILE_NOT_FOUND", "file=" + file, "reason=" + theReason);

You'll notice that there is no Resource Bundle lookups, and no substitutions occuring here.  The information is attached to the exception object in its raw form.  Only when the content is shown to the user (e.g. when it is displayed in a popup, or written to a persistent log file) is the final message formed, using the techniques detailed above.  In the above example, there are two strings attached to the exception (each representing a piece of context that is associated with the error message).  The first string, "file=" + file, denotes the file that has the problem.  openInstaller will attempt to translate the left-hand side of the = sign ("file").  This means that the final message may appear as:

File Not Found.  File=/tmp/foo.txt Reason=Out Of Memory

in French, this may be shown as:

Fichier non trouve.  Fichier=/tmp/foo.txt Raison=pas assez de memoire

By using this throughout the project, openInstaller avoids any translation artifacts and phrases that appear as though a 3 year old child spoke them. 

Also, openInstaller also uses an emerging format for logging messages which allows logs to be translated and re-translated independent of the programs that produced the original logs.  More on this in a future blog. 

How can you use EnhancedException in openInstaller?

As openInstaller is fully declarative, in most cases you won't even need to worry about this.  However, if you are writing custom validation code for a configuration parameter (e.g. asking for a port number, and the port number must be > 1023), then you can throw an EnhancedException when a failure occurs.  This allows the openInstaller engine to log the failure, as well as produce a nicely-formatted message for the user (in all display modes, even CUI and Script/Batch/Silent mode).  For example:

<actions>
<onSet><![CDATA[
theValue = (String) thisProperty.getUnconfirmedValue();
if (theMainPassword != null && !theMainPassword.equals(theValue)) {
throw new EnhancedException("PASSWORDS_MISMATCH", new String[] {"reason=" + reason});
]]></onSet>
</actions>

          

This would appear in your configuration schema (xcs) file which describes the configuration parameters.  When the user enters a value on the associated UI screen, and clicks "Next", this code snippet is run (in addition to any basic validation parameters, like whether a string is really a string, or whether it is an integer within a desired range).  More details on configuration validation can be found at Sandeep's blog.


 

Thursday Jul 12, 2007

openInstaller: GUI and CUI parity, made easy

openInstaller has come up with a revolutionary way for developers to achieve GUI and CUI functional parity virtually for free. By using a combination of several open source projects, including SwiXML, Charva, and nCurses,
the install developer can simply create their UI layout once, in a
single descriptive  ML file (more to come there, a GUI designer tool is
in the works). 

For example, here's a typical license screen in GUI:


 

Here's the same screen rendered in CUI:

 

 

[Read More]

Monday May 14, 2007

openInstaller 0.9.0 released!

A while back our team began to re-architect a somewhat painful part of the experience of Java Enterprise System: the integrated installer.  The problems that Sun and our customers faced were becoming more and more apparent and costly.  We are not finished yet, but a big piece of the solution has become reality: the openInstaller.  This open-sourced project has recently been released under the GlassFish banner as a solution to the problem of integrating a large set of disparate products together into a unified install and ongoing software management experience.  We have started the openInstaller community, and (as with most open source projects) hope to build it with members who can contribute to the project and make it even better (and fix its problems, of course).

Many of its features are obvious for software installers, but there are a number of innovative approaches that separate us from the crowd:

  • Fully declarative engine
  • Expressive and consistent configuration syntax
  • Interfacing with product configurators
  • GUI/CUI parity, for free
  • Multi-install aware
  • Simple, yet very powerful and intuitive dependency model
  • Excellent data/logic separation
  • Default "best effort" behavior
  • Completely modular, easily debugged in the field

 
There are many other features that are sort of "must-haves" (e.g. JDK detection, progress reporting, upgrade/uninstall, logging) which other team members are writing about.  There is also tooling on top of this framework which makes the creation and maintenance of integrated installers a pleasure.  See the tooling demo and download it and try it yourself! I'll try and expand upon some of the more interesting features in future writings.


Thursday Aug 25, 2005

!MAKE MONEY WORKING FROM HOME!

So I have a quiet sigh every time I exit the freeway, only to see one of those real cheap "work from home and make enormous sums of money" signs. In 2001 I became a full time work-from-home Sun employee, and it's not the panacea that those signs make it out to be, nor is it anything like what someone who has never done so might imagine. So here now are some interesting tidbits about it:


James Falkner's official Working from Home FAQ

Q: How can I work from home too?

First, decide if you _really_ want to work from home by reading the rest of this FAQ.

Q: OK, I really want to work from home. Now what?

Well, I really only have experience doing this with one company, so YMMV(Your Mileage May Vary). Build some confidence in your boss that you can be independent. Ask about working from home one day a week. Be available in all possible ways (Phone, IRC, IM, Cell, {insert stupid technology that someone thinks is cool for communicating that I don't know about here}). Get copious amounts of work done (which requires that your job requirements can conceivably be covered at home). Do this for a long time (6 months or so, until it is known that you always WFH(Work From Home) a certain day a week). Then, gradually do it twice in a week every so often. Rinse. Repeat. Then pop the question. Of course it doesn't hurt if you are either an excellent employee, or one that has knowledge that would be a serious bummer to lose to the corporation.

Q: What's it like working from home?

This is the question I get asked most often. To tell you the truth, it's about the same as working from an office. Sure, there are perks, but there are also drawbacks. Read on

Q: Is it difficult to make yourself get up each day and work?

Is it difficult for \*you\* to do the same? Honestly, it's the same. If you are a motivated employee, you will tend to work efficiently no matter where you are. If you are a slacker, you'll probably sleep in and work 4 hours a day, and it'll show, the same way it shows when office-bound employees spend most of their time chatting in hallways, getting coffee, anything to avoid real work. I tend to adjust my schedule to fit around the US/Pacific timezone, since the majority of my colleagues are in that timezone. So on the days that I have late meetings, I tend to start working around 10 or 11am and work until around 8pm US/Eastern. Other days I start around 8am and work until 6pm. The perk here is that I can adjust my schedule, but then again I would do the same as an office employee who has to interact with other timezones on a regular basis.

Q: Do you "get dressed for work" each day?

I try to make sure and take a shower and shave once a month. Cuts down on laundry.

Q: Isn't it a distraction to have your [Television|Refrigerator|Backyard|Car] so accessible?

It's all about motivation. Motivated employees will work. Unmotivated slackers will eat bon-bons and watch soaps while dreaming of that married w/children episode with Jim Jupiter: The Healthiest Man in Chicago episode.

Q: So what are the downsides?

Being full-time remote, you're never in your boss's office or their boss's office. They never see you at all-hands-type meetings. You never run into them in the lunch line. Therefore you have to make a concerted effort to be 'known' so that at raise/promotion time they think of poor ole you slaving away :) Also, career advancement is tougher for remote employees. That's what makes Sun such a great company to work for. As for money, it all evens out in the end. I save money on gas. I spend money on electricity and water. I save money on taxes. I miss out on important meetings where I can influence people and make a name for myself. I can make my own hours. Sometimes those hours extend to midnight or later due to timezones. In summary, it's really about whether your personality will let you work for months on end without seeing or directly interacting with any peers or boss whatsoever. You have to balance your work time with family time. You cannot 'leave work at work' since your work is at home. You have to draw barriers and respect them. Do not check email at 2am on Saturday after finally getting your child to bed. Do not take calls when you would normally be snoozing away. Find the balance young warrior.

Tuesday Aug 23, 2005

The Snatch is Good

So yesterday I finally got the courage enough to try a full-on "snatch movement"using my new bumper plates (a.k.a. solid rubber weights, so I could throw them down if I got into trouble during the lift). I had been practicing the movement with an unloaded PVC pipe for several weeks, and then an unloaded olympic bar (45 lbs.) and yesterday was the first time I tried it with any significant weight, 115 lbs. It felt great to have the confidence that I could bail out at any moment. And the snatch is a full body movement, so you really feel it all over (as opposed to those who like to do endless sets of "concentration curls on a swiss ball";).

There's just something about heaving heavy stuff overhead using a very efficient form. The development of balance, speed, power, strength beats the hell out of endless hours on a recumbant bike and you feel very beat but very rewarded after doing them successfully (or even mildly successfully!) Now I just need to find a local coach who can tell me all of the bad stuff I'm doing.

Monday Aug 22, 2005

"If Computers Worked, I'd Be Out of a Job"

I finally got my blog account set up and this is post numero uno.. \*AFTER A WEEK OF HASSLE\* I'm live. I have no clue what I'm doing out here. Calm down. Imagine that on the internet everyone walks around in underwear. Hmm.. no, that didn't help... now I have nightmares in addition to first-time blog jitters. The title of this post is taken from a quote from rrang, a regular denizen of the "#p/g! IRC Channel" where I once frequented, back in "my college". At the time, I was about a semester away from graduation and looking for jobs (this was in 1997). I knew I didn't want to do hardware or firmware (I had had enough of it in the CS EE degree curriculum, where I spent a lot of time wiring breadboards and blowing up large capacitors and programming fancy LED displays, whoopee). I didn't want to do application software either (I had interviewed with HP and random consulting houses to do just that - blah, blah, and more blah). So that left system programming, and Sun had \*Solaris\* and it seemed like a perfect match... So after winning an offer, and accepting it, I was talking with \*rrang\* (he was administrating some Sun systems for a Canadian university at the time), and he was complaining that his stuff never worked, but if it did he'd immediately be fired. I thought it was a really funny quote because it's so true. I thought it was a great quote and illustrates how much we do \*not\* know we are capable of and also how much unnecessary and crappy complexity is built into today's computing systems. Complexity is the enemy and can lead Bad Things. We need to do a better job of teaching those electrons. As for me, I am a lowly engineer who has worked @ Sun since college, about 8 years, and have worked in a couple parts of the organization, including starting out in Solaris OS Testing, then on Solaris Install (Jumpstart, Flash, and assorted packaging technologies), and most recently the "Java Enterprise System" Install Architecture team. We are driving cost out of our products by \*simplifying\* them, and \*always\* thinking about our customer's perspectives in designing new systems that we'd like them to buy (how innovative!) We are working on some major enhancements to the user experience of owning your very own piece of Sun Software.
About

bytor

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today