Monday, February 25, 2008

Mount SSH/SFTP drive in Windows

In Linux or Mac OS X, when you want to mount a directory in remote machine to your local machine, you just use SSHFS with command sshfs. Like this:
sshfs remoteuser@remotehost:/path/to/remote_dir local_mountpoint

This is not that easy in Windows environment. Windows is unable to speak SSH or SFTP natively. While there are many applications available to use SSH and SFTP in Windows, they don't allow mounting remote directories over SSH as a local Windows drive.

When looking around, I really found no acceptable solutions to this problem.

For starters, sshfs does not work on Windows even with Cygwin, because it needs a FUSE kernel.

Next, there are no open source (or at least freeware) applications to mount over SSH/SFTP. Granted, there are not many commercial applications either. One commercial solution - SftpDrive - seems to be an exact solution to this problem. But as it is commercial, I didn't even try the evaluation version.

There are many rumours about NetDrive (by Novell) lurking around the Internet. But NetDrive is not free (as many falsely claim) and those who have actually managed getting NetDrive to mount a drive over SFTP seem to be few in numbers. There are many sites out there that promote illegal use of NetDrive. Don't use it unless you have a proper Novell license!

In last desperation I tried to use built-in features of Windows. Windows can map an FTP location to your local computer. So, it's only natural to look for an FTP to SFTP bridge and be able to finally mount the remote directory.

Well, not that easy either. All the bridging software for Windows is also commercial. Although there are some free for personal use.

I finally tried out Tunnelier, which is free for personal and limited commercial use.

Final solution is as follows:
  • Use Tunnelier to make an FTP to SFTP bridge.
    • Go to tab "Services", enable FTP-to-SFTP.
  • From Windows Explorer Tools menu -> Map Network Drive choose option "Sign up for online storage or connect to a network server".
  • Enter FTP URL for the location you just created in Tunnelier.
  • Enter the same authentication credentials you use for SFTP.
  • Windows mounts your remote directory as a network location.

All is well that ends well? Not really, there are still a few remaining problems:
  • Windows can't actually mount an FTP location to a drive (i.e. you get no drive letter). Windows only makes available a "network location".
  • You can't mount an arbitrary remote directory. You always get the home directory (or a subdirectory below it) for the user who connected to remote server (limitation of Tunnelier).
This is still better than nothing.

Update: It appears that you can authenticate the FTP end of the bridge any way you like (anonymous or bogus username, bogus password) - you don't have to use the same credentials as for SFTP.

Friday, February 22, 2008

xsi:noNamespaceSchemaLocation

Validating an XML file I was able to get the following error:
Expecting namespace 'null', but the target namespace of the schema document is 'null'.

'null' seems to match 'null', so what's the problem?

It turns out that the validator is Java-based and 'null' is not a literal namespace, but actually a Java null pointer. Meaning that both the XML document and the schema have no namespace.

But why should that be a problem?

It shouldn't. As the XML file was generated from XML Schema file (XSD), it contained attributes similar to the following:
xmlns:ns17='null' xsi:schemaLocation='null path.to.schema.file.xsd'
Additionally, root element (nor other elements) didn't have the ns17 prefix.

This is just plain wrong. Although it is not pretty to have schemas without namespace, it is also not pretty to generate such crappy XML from it.

A quick fix by hand would remove the namespace definition and replace schema location with:
xsi:noNamespaceSchemaLocation='path.to.schema.file.xsd'

Voilà!

Reference: http://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation

Tuesday, February 19, 2008

Incorrect version of MSJET35.DLL found

When you receive such error message while trying to open an Enterprise Architect project file (.eap), don't worry, the solution is simple. Actually, this error can be associated with any program that uses Microsoft Access database files as it's data containers and the solution is always the same.

First you have to update MS JET 3.5 (http://support.microsoft.com/kb/172733).

When that doesn't help, you can also try updating MDAC (http://msdn.microsoft.com/data/ref/mdac/downloads/) or install (or update) MS JET 4.0.

When that still doesn't help, try restarting your Windows (known to help with all kinds of problems).

When that doesn't help either, search your system for any stray MSJET35.DLL files and remove or update them.

Useful read: Deployment of Enterprise Architect (includes instructions on using JET 4).

Thursday, February 14, 2008

Using Parallel Pipelined Functions in PL/SQL

Do you have a serial PL/SQL code reading data from table, doing complex computations and putting it back to the database?

DECLARE
BEGIN
FOR x IN (SELECT * FROM t) LOOP
-- Do complex calculations with x
INSERT INTO t2 (col1, col2) VALUES (x.calc1, x.calc2);
END LOOP;
END;

Want to speed it up? You can turn it to running in parallel using a pipelined function.
This feature allows you to read from a function as if it was a regular table and the best part of it is, that you can do it in parallel.
So, first you have to "change" the procedure logic a little, you will have to write a function doing the complex calculation part and returning the result as a cursor. Then you can issue one regular parallel DML command to insert the data to a new table.

Let's first create the tables.

CREATE TABLE t1 AS
SELECT object_id id, owner FROM all_objects
UNION ALL
SELECT object_id id, owner FROM all_objects;

CREATE TABLE t2 AS
SELECT id, owner, 111222333 random_number FROM t1 WHERE 1=0;

exec dbms_stats.gather_table_stats(ownname => user, tabname => 't1');

Then create the object types, that the pipelined function will be returning.

CREATE TYPE t2_type AS OBJECT (
id NUMBER,
owner VARCHAR2(30),
random_number NUMBER
)
/

CREATE TYPE t2_table_type AS TABLE OF t2_type
/

Now the pipelined function part.

CREATE OR REPLACE FUNCTION complex_processing(l_cursor IN sys_refcursor)
RETURN t2_table_type
PIPELINED PARALLEL_ENABLE(PARTITION l_cursor BY ANY)
IS
l_rec t1%ROWTYPE;
p_random_number NUMBER;
BEGIN
LOOP
FETCH l_cursor INTO l_rec;
EXIT WHEN l_cursor%NOTFOUND;
-- Complex calculation goes here
p_random_number:= DBMS_RANDOM.NORMAL;
--
PIPE ROW(t2_type(l_rec.id, l_rec.owner, p_random_number));
END LOOP;
CLOSE l_cursor;
RETURN;
END;
/

Here the PARALLEL_ENABLE clause tells Oracle that input can be processed in parallel and let Oracle to determine the best way to split up the data between parallel slaves. If you need some specific splitting of data, for example need that the slices are not overlapping, then modify the PARTITION clause.
First lets see what happens if we just issue a SELECT statement on this function:

SQL> set autot traceonly explain
SQL> SELECT * FROM TABLE(complex_processing(CURSOR(SELECT /*+PARALLEL(t1)*/ * FROM t1)));

----------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 1148K| 20 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10000 | 8168 | 1148K| 20 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 3 | VIEW | | 8168 | 1148K| 20 (0)| 00:00:01 | Q1,00 | PCWP | |
| 4 | COLLECTION ITERATOR PICKLER FETCH| COMPLEX_PROCESSING | | | | | Q1,00 | PCWP | |
| 5 | PX BLOCK ITERATOR | | 83912 | 819K| 11 (10)| 00:00:01 | Q1,00 | PCWC | |
| 6 | TABLE ACCESS FULL | T1 | 83912 | 819K| 11 (10)| 00:00:01 | Q1,00 | PCWP | |
----------------------------------------------------------------------------------------------------------------------------------------

Looks nicely parallel to me!
Now, let's insert the data using parallel DML.

ALTER SESSION ENABLE PARALLEL DML;

INSERT /*+APPEND*/ INTO t2(id, owner, random_number)
SELECT * FROM TABLE(complex_processing(CURSOR(SELECT /*+PARALLEL(t1)*/ * FROM t1)));

COMMIT;

Done!

I first saw this technique in Tom Kyte's book Expert Oracle Database Architecture. Get this book!

Wednesday, February 13, 2008

Firefox 3 Beta 3 released

Firefox 3 Beta 3 has been released.

I still find the full page zoom feature very disturbing. It does not let you zoom text on poorly designed pages, instead it zooms all the layout, resulting in close to zero benefit. Opera seems to be much more user friendly in this aspect. A big step back.

Another feature, described as "Tab scrolling and quickmenu: tabs are easier to locate with the new tab scrolling and tab quickmenu." in the release notes, bothers me to a great degree. As someone who has many-many tabs open at all times, I need to use tab scrolling and quickmenu a lot. But I feel that comparing with Firefox 2, the new way is less functional and definitely harder (and slower!) to use. Another big step back.

Otherwise Firefox 3 is quite usable. I have been using beta 2 for a couple of months now and besides the aforementioned grunts plus a weak download system and invasive location awesomebar, it's almost solid rock to use. Still, others see great performance improvements, but to me Firefox is as slow as it has always been.

Update: Beta 3 seems to be significantly slower than Beta 2. Maybe some debugging code...

Friday, February 8, 2008

Having trouble with SOAP headers and JAX-WS RI?

There are many WSDLs out there that define SOAP headers in binding section, but not in portType section. More often than not this is not intentional (mostly found in .NET generated WSDLs) and can cause unwanted side effects. One of these effects is that JAX-WS RI wsimport tool won't generate header parameters for operations.

As a solution you can always cast your port object to WSBindingProvider and use setOutboundHeader function, but that's a bit messy. JAX-WS documentation goes even further saying that "The portable way of doing this is that you create a SOAPHandler and mess with SAAJ". Ugh, not good.

But there is one simple, clean and easy solution. If you are using JAX-WS RI 2.1.3 (released December 18, 2007, Metro 1.1 includes it) or newer then wsimport has a new option: -XadditionalHeaders. Use this option when you have a WSDL with additional headers that are not part of portType contract.

Thursday, February 7, 2008

How to catch those unwanted and uncaught exceptions before they reach the user

Users of your applications certainly don't want to see any cryptic Java exception messages and stack traces. So, how do you catch and process all uncaught exceptions before they reach some level in stack, e.g. before the user interface in a web application? This can be troublesome when you don't have a single entry-point from web interface to service layer.

Well, one way you can do it, is to leverage AspectJ.

@Aspect
public class ErrorsAspect {
@AfterThrowing(pointcut = "execution(public * com.example.your.package..*Controller.*(..))", throwing = "t")
public void handleUserVisibleException(Throwable t) {
.. log ..
.. send mail ..
.. etc ..
}
}

This kind of advice re-raises the exception after it's done, so you might want to use an Around advice and catch the exception if you don't want it to progress. Remember that you can write more complex pointcuts when you need information on target objects or function arguments.

However, if you want to output a pretty error screen to the user and you use Spring MVC as your web framework, then just implement the HandlerExceptionResolver interface (it can be as simple as a one-liner) or use the provided SimpleMappingExceptionResolver.

Wednesday, February 6, 2008

Universaalne (arvuti|interneti)keel?

Sain täna reklaamkirja ühelt küllaltki suurelt ja ehk isegi mainekalt koolitusfirmalt. Kirja sisu tavaline "koolitage end nii- ja naapidi...", mille võib peeniseravimite, Nigeeria onu päranduste ja aktsiaostusoovituste kõrval isegi üsna rahulikuks liigitada. Mis aga kulmu kergitama pani, oli viimane lause kirja lõpus. See kõlas umbes nii (rõhud minu lisatud): "Kui te ei soovi enam sellised kirju saada, tehke käesolevale e-mailile reply, mille subject real on REMOVE".

???

Vägisi tuli meelde anekdoot, mis kõlab:
"
Dorogoi friend!

Mind volnuujet one kysymys. That dvuhkeelsuse story.
Mitmelt poolt moshno bõlo lugeda, that kahden kielen
oskus vlijajet nehorosho. I have svoi seisukoht
tässä kysymyksessä. Nimelt olen juba varasest agest
saati tshtõrekielinen. And vsjo okei. Äiti õpetas
estonskit, Soome TV finnskit, keeltekoolis õppisin
angliiskit, Soviet Armys russkit.
Yhteistulos on hämmastav - new language! Kui kõik
jõuaks so far, oleks kaikki kunnossa.
Igat jazõkki natuke and everybody ponimajet, mida
keegi govorit. Otshevidno, et see võiks olla natshala
dlja new international kieli, kymmenen times better
tshem esperanto. Pole ka vaja bojatsa, that estonskii
keel hääbub - naoborot, tämä kieli teeks meistä
meshdunarodno famous. Kutsun üles kogu narodi
oppimaan tätä keelt. As you can see, on seegi statja
fourkeelne, aga koje-shto can ymmärtää predstaviteli
tshetõrjoh narodov!
Vot milline kasu! Eesti people - ärge mõshlite enää!

Future on tshetõrekieliste päralt!

Jaan-John Kuznetsov-Seppälä
"

Kas ongi nii?