BibleGateway.com Verse Of The Day

Showing posts with label tips. Show all posts
Showing posts with label tips. Show all posts

Wednesday, May 23, 2012

OSB Table Poller Issue Setting Status

After building quite a few Oracle Service Bus table pollers, my last project should have been something I could do with my eyes closed. Poll a table looking for certain 'actions', and call a few web services to make that 'action' happen in another enterprise system.

A very simple service flow:
1. JCA table poller adapter, configured for logical delete (change an indicator from one value to another), set up for distributed polling so it will work in a clustered environment. Basically look for records with status of N, switch it to P.
2. Wrap that in a proxy that simply routes to one of several other proxies based on the 'action'. (local transport)
3. Those other proxies call web services based on the values in the original message. On success, the response pipeline will set the status to C. If any of those services fails or returns an error, an error is raised to the proxy's error handler, which sets to the status to E.

That's it, dead simple. If there are no errors, everything works great, record goes to C, we're all good. 

However, if an error is raised on the request pipeline, the status doesn't get changed to E. Using the same service callout that works for C, does not work for E.

I watch my WebLogic logs and my TOAD query at the same time and notice some things:
1. For a success, I see in TOAD the record stays in N status until all the web services have been called. Once the final service returns, it immediately goes to P then C. All good.
2. If an error is raised along the way, or even if I manually try to set the status anywhere on the request pipeline, that call hangs until the transaction timeout occurs, then that update to E fails, as well as the update to P. End result, the record stays in N and gets pulled again, and again, and....

(As an extra red herring to keep me busy, I get a log full of XA transaction type errors, so I spend way too much time working with the DBA trying to set up XA permissions.)

So it seems that all along the request pipeline, the OSB has that row locked for update, but hasn't done the update to P yet. If I try to set it in my flow, the two updates deadlock until the XA timeout occurs. However, when setting it on the response pipeline, the update to P has already happened, so it works there.

I could have rewritten all my proxies to only change the status on the response pipeline, but that would have been a painful rewrite of the flows. An easier solution was to have the poller proxy immediately drop the message on a JMS queue, and then have a queue listener proxy pick those up and route them. Doing this, I see the record go to P immediately, no matter what, and then success or failure, my status changes without any timeouts or XA errors.

Monday, November 01, 2010

Oracle Service Bus - Getting XML Out of Table Column

This took me a while to figure out, and ended up being pretty simple once I knew which XQuery functions were available.

Let's say you have a table you need to query from the OSB. The result set you get back from the JCA DB adapters gets turned into XML for you. If one of the columns is a LOB containing XML, then it gets escaped using CDATA. Anything in CDATA is basically ignored by XML, XSL, XPath, etc.

It is a 2 step process to extract that value and make it usable XML:

  1. Use the fn:string() function to turn the value into a string. In this case, it strips off the CDATA wrapper and gives you a string that looks like XML.
  2. Use the fn-bea:inlinedXML() function to parse that string into XML.

You can use an ASSIGNMENT task to jam that XML into a variable, and then run any XSL, XPath, etc on it. You can also use INSERT or REPLACE tasks to embed it back into your original XML.

Enabling Oracle Trace For WLS Connection Pools

We recently had the need to run an Oracle trace to catch some database diagnostics for an OSB call flow. To enable the trace only for the sessions coming from WebLogic, I added the following to the "initSql" field in the connection pool setup screens (in the WLS console). Just remove it and save the pool settings when you're done collecting your *.trc files. Works like a charm.

SQL BEGIN DBMS_MONITOR.session_trace_enable(waits=>TRUE, binds=>TRUE); END;

Wednesday, October 13, 2010

A Few More SEAM Nuggets

I just came across some stuff that was supposed to be a blog post with some more SEAM tips and tricks. Since then, I've moved on to a new job, and haven't been using SEAM in my new position.

Showing Number of Matched Records in List View

When you generate a SEAM application from a database, you end up with a set of components for each table - for example a list view, record view, and record edit. When you are displaying a list of records, it is common to want to show how many records match the current query (or the total records if you went to list view without a search criteria).

You can easily get this function by accessing the "resultCount" attribute on your view, like this:

<h:outputtext value="#{yourTableList.resultCount} rows found." rendered="#{not empty yourTableList.searchResults}">

Change Default Sort Order, But Still Allow Click-To-Sort

One requirement we ran into on one of my SEAM projects was to change the default sort order for some of the list views. However, the views that the SEAM gen tool creates allows the user to click on the column headers to change sorting as well. To achieve a default sort order on initial load, and still allow clickable headers, override the getOrder() method in your List.java source like this:

@Override
public String getOrder() {
String order = super.getOrder();
if ("".equals(order) || order == null)
{
order = "col1 asc,col2 desc";//your default sort columns here
}
return order;
}


Wednesday, August 18, 2010

Oracle Sorting of NULL Values

Speaking of Oracle crap, a few weeks ago while working on this project, the question came up of how Oracle sorts NULL values in an ORDER BY clause. I looked it up, and learned something new. Hooray for learning new stuff. I didn't realize you can specify how to sort NULL's in your ORDER BY clause. You add "NULLS FIRST" or "NULLS LAST" like this....

SELECT * FROM sometable ORDER BY col1 ASC NULLS FIRST

ORA-24777 When Using XA Driver

Here's something we came across this past week, and after some searching, it appears to be a fairly common issue.

We are calling some PL/SQL stored procedures through JCA adapters on the Oracle Service Bus (OSB). Our connection pool on WebLogic is setup using the XA JDBC driver.

Everything was great until we called a stored procedure that queries across a database link. Then we got the dreaded ORA-24777 - use of non-migratable database link not allowed.

Turns out there are at least 3 ways to rectify this issue....

  1. Set up Oracle to use multi-threaded server, a.k.a. shared server. There are ups and downs, and depending on who you talk to, mostly downs, especially concerning performance. We haven't tried this, but it does come up often as a fix.
  2. Create a "shared" database link. The syntax is a little different than a "normal" link. This is what we did, and it worked fine.
  3. Third option, though not right for everyone, would be to use the non-XA driver.
Creating a shared link....
CREATE SHARED DATABASE LINK 
CONNECT TO IDENTIFIED BY
AUTHENTICATED BY IDENTIFIED BY
USING ;

Wednesday, October 21, 2009

Running Multiple Versions of Oracle JDBC Driver on same JBoss server

We ran into an issue recently where we need 2 different versions of the Oracle JDBC driver on the same app server, because one application needs to talk to Oracle 8.0.6 (pre-8i even) and everything else talks to newer (9i and 10g) databases.

With the newer JDBC driver out in the Jboss server's lib/ directory, the legacy application fails to connect because of the dreaded ORA-604 that you get when trying to connect to a really early DB with a driver that doesn't go back that far. See the links below for Oracle's list of what drivers support what versions of Oracle. The exception is shown below in case you haven't seen it before.

Throwable while attempting to get a new connection: null
org.jboss.resource.JBossResourceException: Could not create connection; - nested throwable: (java.sql.SQLException: ORA-00604: error occurred at recursive SQL level 1
ORA-02248: invalid option for ALTER SESSION

Putting the old driver out in lib/ means that the newer datasources won't deploy properly.

After a few hours of Google searches and pouring through the Jboss.org wiki and forums, we just couldn't find the answer. We finally made this post to the Jboss forums:
We are using Jboss AS 4.2.2-GA and have hit a snag. One application needs to connect to an older Oracle 8 database, so we need to use the older Oracle JDBC driver. For all of our other applications, we need to use the newer Oracle JDBC driver.

But the classnames for the drivers are the same, so we can't figure a way to tell one *-ds.xml file to deploy under the early driver and everything else to use the newer driver.

We tried adding the older driver JAR file and the *-ds.xml file right in our application EAR file, but when deploying it picked up the newer Oracle driver from the server's lib directory anyways. This puzzled me as we are using ear-scoped classloaders.

Both applications are SEAM applications using EJB3/Hibernate. We can get one or the other to work depending on which Oracle driver we put in lib/ but can't get both to work at once.

I've been searching Google and the Jboss forums and wiki for the past 2 hours, and find lots of newbie "this is how to deploy a datasource" stuff, but nothing on 2 conflicting versions of a vendor's driver. HELP!
We got the response the next morning, and it turns out we were on the right path by putting the datasource file and classes12.zip in the application EAR file, but because of JDBC classloader issues, we had to also include a few other files and some extra application.xml config. Here is the complete article.



Links:

Tuesday, August 18, 2009

Multiple Column Subqueries (Oracle)

I recently had to do something like this in an application, and I had to look it up to see if it was even possible. I knew you could do this with a single value, but was skeptical if multiple columns could be done this easily.

For my own benefit, I am posting the basic syntax for next time I need to do it. Replace your table names where t1 and t2 are, column names for c1, c2, c3, etc. I did this in Oracle, but it may work the same or very similar in other databases.

SELECT *
FROM t1
WHERE (c1,c2,c3)
IN (
SELECT t2.c1,t2.c2,t2.c3
FROM t2
)

For reference, I found my answer at: http://www.java2s.com/Code/Oracle/Subquery/WritingMultipleColumnSubquerieswithtablejoin.htm

Wednesday, July 15, 2009

More Jboss SEAM Nuggets

As I dig farther into the Jboss SEAM world, I figured I would share a few nuggets of info as I find them. It's more notes than tutorial, so don't expect too much....


ORACLE TIMESTAMP ISSUES

If you are using Oracle, more specifically Oracle 9i and newer, you will probably have issues with date fields. There was a change in Oracle's JDBC driver somewhere between 8i and 9i that changes how the driver reports a DATE field in the metadata. Anyways, running seamgen gets you entities with Date objects with annotations like @Temporal(TemporalType.DATE).

When you go to deploy though, The validation fails because it is expecting a TIMESTAMP field, but gets a DATE field. That keeps the EJB from deploying and keeps the EntityManager from being bound.

Luckily, there is an easier fix than manually updating the data types and annotations in all your generated entities. You can add the following JVM argument to your startup to force the Oracle driver to report DATE fields based on the Oracle 8 driver behavior instead of the 9i+ driver:
-Doracle.jdbc.V8Compatible=true


RENDERING ISSUES ON SERVER

Speaking of JVM args, I noticed on my dev box (Windows), my SEAM app rendered beautifully. When I deployed to the Linux server, some of the fancy "3d" type components suddenly looked flat. There were exceptions in the log for some Swing classes (sorry I don't have the exact exception or stack trace, it was a while ago). Why is it using Swing to render page elements? Don't know, and at this point, don't care, as long as there is a simple fix. And alas, there is...

I added the following JVM argument to my Jboss startup to let it know it's a headless server. Seems to have fixed my issue.
-Djava.awt.headless=true


MULTIPLE PAGE DEFINITIONS FOR ONE PAGE

When you build out a new application using seamgen, you will notice that for each entity, you will have several view components. For example, you will have a TableName.xhtml and a TableName.page.xml file. The *.page.xml contains your <page> definition, that will define the view id, parameters, conversation settings, etc.

You will also notice that the SEAM pages.xml file also has (or can have) <page> definitions. Guess what, the pages.xml definition for a given page, if it exists, overrules the *.page.xml version. Remember that when changes you make to *.page.xml don't seem to take effect, or other odd behavior where those values seem to be ignored.

It's not a hierarchy, it's not an inheritance, it's a one-or-the-other situation. It caused me quite a bit of grief until I figured that out.



USING SEAM COMPONENTS FROM SERVLETS

So you need to throw in some regular old-school Servlet's into the mix? But you want your SEAM goodies too? Damn, you just want the best of everything.

There are a few ways to do it, and I found the easier way is to "wrap" your Servlet(s) in the SEAM filter. Add a line something like this to your SEAM components.xml file (make sure to change your URL pattern appropriately. In my case my Servlet is to download a CSV file).
<web:context-filter pattern="/csv"/>
And then in your Servlet code, you can get your SEAM stuff from the Components object like the sample below:
UnmappedAccount unMapAcct = (UnmappedAccount)Component.getInstance("unmappedAcct");
In this example, UnmappedAccount is a class in my EJB module annotated as a SEAM component.
import org.jboss.seam.annotations.Name;
...

@Name("unmappedAcct")
public class UnmappedAccount { ... }


CASCADING EJB'S

Allow me to throw out a minor warning on the generated SEAM code. The default CascadeType in the generated EJB's is "ALL", as seen in the annotation below.
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "...")
It's not devastating, it's not a bug, but it is something you should be aware of in case you don't want cascading (or worse, aren't aware of cascading and what it really means, and just happily go with the defaults).

See http://java.sun.com/javaee/5/docs/api/javax/persistence/CascadeType.html for more information on the various CascadeTypes, in case you don't already know. If you don't want cascading, remove the "cascade = CascadeType.ALL" portion of the generated annotation.

Let's say you try to delete a record that has children records attached to it. You might expect to get an error back saying you can't delete a record that has children attached. That's what you would get from, say TOAD or Squirrel or SQL-Plus, or even from applications written using plain old ODBC or JDBC statements. But with cascading, that delete will happily take care of those children for you without complaining ;-)



NAMED QUERY DEFINED IN COMPONENTS.XML

I just found this one, and it seems to be a good fit with what I have to do right now. I haven't finished it yet, so not much to share here yet. Once I get that working, it might be worth mentioning in my next nuggets post.

Thursday, May 28, 2009

Emergency Startup CD for Windows

BartPE, because sometimes you actually have to run Windows.

If you have Windows and your system did not come with a "rescue", "restore", or "emergency startup" type CD (yes, I'm talking to the cheap bastards at Dell who don't include the Windows CD), then you NEED to follow the instructions at http://www.howtohaven.com/system/live-windows-rescue-cd.shtml. This creates a bootable "BartPE" CD with all the right utilities for fixin' what ails ye.

Do it now! Before you need it, not after. I learned the hard way, and after this past week, I can't sing the praises of BartPE enough. It was a life saver.

My wife's laptop booted to a BSOD when I was trying to VPN in to work. Oh well, a night off....but then it kept doing it, over and over. I couldn't get Windows to start for the life of me.

Since I didn't have a bootable Windows CD, I was left trying to boot FreeDOS from my thumbdrive (using LiveUSB Creator) . I made sure to have a copy of NTFS4DOS installed on there as well, and ran through some exercises where I got a command prompt to manually copy registry files around. That was painful, but it actually worked enough to get the machine to boot into XP. But it was a version of XP that had all sorts of issues, error messages, drivers crashing, no USB support, no CD recording, no network, etc. Without USB, network, or CD burning, I couldn't get our important files off the machine.

Anyway, to make a long story short, the FreeDOS/ntfs4dos route was crappy and painful with no real reward.

The BartPE rocks the house! It boots into a minimal XP interface with all the right utilities for fixing your issues, and USB drives are recognized, so I can copy important files off the machine in case I feel the need to wipe it clean.

I guess I really should do regular backups, but what fun is that? Preventing problems is boring, solving problems is heroic....

Tuesday, March 03, 2009

An Overzealous SEAM Validation

I am using the Jboss SEAM framework for the project I am currently working on. The first thing that really bit me was a validation on single character fields that seems a bit "too stringent". I'm sure I'm not the first to run into this. In fact it may be fixed by now in a newer version of SEAM, but figured it's worth a post anyways.

When you use Jboss Developer Studio (JBDS) to reverse engineer your database (I imagine the seam-gen tools do the same since JBDS uses them), the getter method on the EJB looks something like this...

@Column(name = "DEFAULT_REMAP_IND", length = 1)
@Length(max = 1)
public Character getDefaultRemapInd() {
return this.defaultRemapInd;
}
Looks fine, looks reasonable. Single character fields are often used for flags or indicators, as this one above is -- it's a simple Y/N field. So in my XHTML I use a radio button control instead of a free form text field.

<h:selectOneRadio id="defaultRemapInd" value="#{ocamCustTypeDfltHome.instance.defaultRemapInd}" required="true">
<f:selectItem itemValue="Y" itemLabel="Yes" />
<f:selectItem itemValue="N" itemLabel="No" />
<a:support event="onblur" reRender="defaultRemapIndDecoration"/>
</h:selectOneRadio>

The SEAM framework will use that @Length annotation to perform some field validations for you automagically, which is pretty cool as it can be a real time-saver. But the funny thing is that when this "max length of 1" validation fires, you get the lovely message shown in this partial screen shot ....

Not sure how to get a single character with a length greater than 0 but less than 1, but I do know that the easiest thing to do in this case is remove the @Length annotation for that field. I had to do that for a few different tables. Luckily they were all flags that could be replaced with radio buttons, but in the case where you had to allow free input, you might have to get more creative (you can always put a max length on the input text field).

For reference, I am using Jboss 4.2.2, SEAM 1.2GA, Jboss's EJB3 implementation (which is Hibernate under the hood), XHTML and Facelets for the views. Like I said, this might be fixed in a later release of SEAM.

Monday, August 18, 2008

Simple JSP To List Java JVM Environment

Here's a simple JSP to list out all the JVM environment keys and values. Very simple, but occasionally useful. For example, a few weeks ago I needed to see which SSL keystore (javax.net.ssl.trustStore) our test servers were using.

I'm sure every Java developer has written similar code at some point, so I won't claim it's original, unique, or ingenious. But if you find it useful, go ahead and use it instead of writing another.


Wednesday, July 16, 2008

Making Your JVM Trust Those SSL Certificates

Guess I should follow-up with a "part 2" on yesterday's post about saving off SSL certificates. The whole point of me going through the exercise was that one of the web services we consume is SSL and the certificate expired. The new certificate was self-signed, so our Java code threw exceptions saying a trusted certificate was not found.

So the second step for me was to import them so my JVM(s) would recognize the certificate as "trusted".

To get your JVM to trust the certificate, you import it into your keystore using the keytool executable (found in your JDK bin directory):
[jboss@j2apptest01 bin]$ ./keytool -import -alias SomeWebserviceName -file ~/SomeCertificateFileName.CER
If the keystore does not exist yet, the tool will prompt you to enter a keystore password. Remember that password, as you will need to use it to import new certificates or export or view current ones.

It will then display all the keys and other info about the certificate and ask you to confirm that you really want to import. You will want to verify the keys match up to what you think you are importing, of course. Then type "yes" and it should tell you the certificate was added.

After that, our calls to the web service started to ork again, like magic.

Tuesday, July 15, 2008

Saving SSL Certificates From A Website

These instructions saved by butt recently, so I have to post so I don't lose them. I was able to figure out how to save the certificate OK, I just didn't know how to get the actual *.CER file so I could import to my JVM's keystore. I didn't know about the Windows app "certmgr.msc"... http://balajiramesh.wordpress.com/2007/12/28/save-ssl-certificate-to-cer-file/

Thursday, May 01, 2008

Some Old-School JAM and JPL Tips

The JAMster Speaks...

I was cleaning out the barn and found a CD of some old code and documents I had written at an old job, kind of a "portfolio" CD if you will. We used JAM to build Motif screens.

JAM let you visually build your screens, and had it's own procedural programming language, JPL (JAM Programming Language). JAM came with the necessary C code to handle launching of the screens and handling events. You could write your own custom C code to be called from the screens, or write your code in the JPL file for the screen, or a combination of both. One nice thing is you could write SQL code right inline with the rest of your JPL (just prepend the keyword "sql") and everything was taken care of, even down to binding the result set to variables.

It's not exactly cutting edge technology now, but I know it is still in use by at least a few companies. This is not a tutorial for non-JAMsters, but rather a collection of troubleshooting tips I had taken note of. Maybe this could be of use to someone just inheriting legacy apps, or who knows when I might run into JAM again. Yuck!

For reference, these tips are mostly for JAM 5 and 7, but they may be applicable for other versions as well.
























SELECT 1 FROM table...This is often
done to see if a record meeting your criteria (where clause) exists in
the table.
Doing this from a JAM screen (at least in Jam 5) will produce a
@dmrowcount of
0 (zero), even if there are records in the table meeting the criteria.
There
are two alternatives within JAM to get the same information:

1. SELECT
COUNT(*)
temp_var FROM table...

2. SELECT
column temp_var FROM table...
SELECT column FROM
table
This one can be tricky.
If there is no JAM variable with the same name as the the selected
columns,
then JAM will create those variables and put the results of the query
in those
variables. If the variables do exist already, JAM will overwrite the
values
with the results of the query, even when the query returns nothing (it
will essentially
clear the variables).
Mixed Case VariablesMixed case variables
can be a real problem with JAM, and the error messages are not
intuitive enough
to let you know what the real problem is. Keep tihs in mind when you
can’t
figure out some quirky behaviour, and all the other logic and syntax is
proper.
Changes in screen file don’t seem to take effectCheck the local
directory you are running from. JAM will use screen, JPL, and
dictionary files fro mthe current directory before
checking in the SMPATH, regardless of what the SMPATH is set to.



Check the data
dictionary. The screen fields and variables can be stored in here, as
well as
in the screen definition file. Data dictionary tends to overwrite
local (screen) vars.
Some Useful environment VarsMost of the relevant environment variables
start with SM, e.g. SMVARS, SMPATH

  • Also worth noting is that while it might make sense that they would just make use of the underlying Motif widgets, in some cases that was not the case. Simple text boxes for instance would not work with bi-directional Hebrew whereas pure Motif screens did. We had to have JYACC / Prolifics issue a patch for us before we could release to an Israeli customer.
  • There were also issues with some characters in the Turkish (ISO-8859-9) character set. Their database layer simply could handle it for some reason. Wouldn't bring back results, wouldn't update, and yet We ended up having to write our own generic database access layer using Pro-C, and replacing all the JPL "sql" statements with "call" statements into our library.

Thursday, April 10, 2008

A Servlet Filter For Easier Debugging

I whipped up this quick Java Servlet Filter (javax.servlet.Filter) to aid in development and debugging. This simply dumps all the HTTP request attributes and parameters and Session attributes to the log. Like any other Servlet filter, it can be chained with other filters like the timing filter, GZIP filter, etc.

This isn't meant to be a tutorial on Servlet filters, I am assuming you either know (or know how to figure out) what filters are, and how to tie them into your application in the web.xml descriptor.

I "printed" the source into a PDF to keep the formatting in tact. Here is the link, or if your PDF plugin is working, it should appear in an iframe below....



Monday, March 17, 2008

Oracle on Windows Authentication Woes (And Solutions)

For my current project, I need to have my own development Oracle instance. Instead of carving out space on one of the servers, I figured it's a good time to just bite the bullet and install Oracle XE on my desktop (Windows XP). For the most part, it has been a fairly painless experience.

Except that several times already, when trying to run imports or simply connecting via SQL-Plus, I get the following crap:
C:\oraclexe\app\oracle\product\10.2.0\server\BIN>sqlplus XXXXXX/XXXXX

SQL*Plus: Release 10.2.0.1.0 - Production on Mon Mar 17 08:50:29 2008

Copyright (c) 1982, 2005, Oracle. All rights reserved.

ERROR:
ORA-12638: Credential retrieval failed
The first time it happened, I simply restarted the OracelServiceXE and OracleXETNSListener services. Then it happened again today, and restarting services had no effect. And I am NOT rebooting my whole machine just to get Oracle to work.

But thanks to someone named "babu george" on this forum, I fixed the issue quite simply by getting rid of NTS authentication.

In your sqlnet.ora file, simply comment out the (NTS) line and create a new line with (NONE) in place of (NTS).
#SQLNET.AUTHENTICATION_SERVICES = (NTS)
SQLNET.AUTHENTICATION_SERVICES = (NONE)
Alas, don't get me started on how much I love Windows.

Friday, March 14, 2008

Trashy Yet Effective Oracle Trick

I need to post this so I can remember it next time I screw up...


I was running an Oracle import (imp) on my local Oracle 10g XE instance today, and realized I did it incorrectly and imported everything into the SYSTEM schema. Oh crap, I can't just wipe everything out, it's my SYSTEM schema! I need surgical precision to only remove the crap and leave all the Oracle stuff unscathed.

I could go through and drop each table I created, but there were hundreds.

Then, like a tornado in a trailerpark, it hit me. An idea that walks the fine line between genius and insanity. All the new table names started with a common prefix, which for sake of discussion I will call "BLAH_". So in Squirrel I run the following query:

select 'drop table system.' || table_name || ';' from all_tables where owner = 'SYSTEM' and table_name like 'BLAH_%'

Then cut and paste the results back into the Squirrel query runner and run the results as a script.

I'm sure there's an "official" way to accomplish the same thing, but screw that, this was extremely quick and kind of fun.

Trashy yet effective.

Thursday, February 07, 2008

Reading And Writing Text Files in Ruby

On a recent project, I had everything automated up to the point where I had to give feedback to the business analysts on what, if any, of their data had to be fixed and re-run.

I ended up writing some Ruby scripts to run through a file of transaction responses, and sort out the various error types into separate files, and produce a summary report of the error counts in each category.
Now the only manual part was cutting and pasting the summary into an email, and attaching the various sorted files.

I was pleasantly surprised at how easy it was to read and write text files in Ruby. Let's say you just want to read a text file, line by line, and spit it back out, a la "cat"...
File.new(filename, "r").each { |line| puts line }
Wow, that was easy, eh? Let's try reading that file into an array for use later...
results = []
File.new(filename, "r").each { |line| results << line }
Let's say you want to capitalize everything in the file, and spit it out to a new file...
out_file = File.new("upper_case.txt","w")
results.each do |line|
out_file.puts line.upcase
end

That was easy. It's probably obvious, but the first argument passed to the File constructor is the filename, and second is the read/write flag.

For comparison' sake, let's read a file in Java and spit it out line for line...
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
...
BufferedReader is = new BufferedReader(new FileReader(fileName));
String line = "";

try {
while (line != null)
{
line = is.readLine();
System.out.println(line);
}
catch (Exception e) { /* do nothing for now */ }
finally
{
try {is.close();}catch(Exception f) { }
}

Definitely not "hard", but it is a bit more code. Writing a file is very similar, except of course you use FileWriter and BufferedWriter instead of FileReader and BufferedReader, and write to the file instead of reading from it.

I'm not really trying to prove anything here -- Ruby isn't better than Java, Java isn't too hard or too verbose, etc. I like Java. I like Ruby. Against my better judgment, I even like Oracle, but that's another topic. I just found that working with files was easier than I expected, even for a Ruby beginner like myself.

Thursday, September 06, 2007

PDFCreator

Apps like OpenOffice.org have built-in PDF export functions, and Mac OS X has a built in PDF export in the print dialog.

But if you're running apps on Windows that don't have this feature, you can download and install PDF Creator. It installs itself as a "printer" on your PC. When you choose to print from any application, choose this printer instead of your normal printer.