Friday, February 27, 2009

Mount SSH/SFTP drive in Windows, take 2

Remember my last rant about how difficult it is to mount remote drives in MS Windows environment?

Well, now it seems that there actually is a way to get FUSE functionality and SSHFS in Windows. There is a project called Dokan.

Take a look here: http://dokan-dev.net/en/about/
Download here: http://dokan-dev.net/en/download/

Haven't tried it out myself yet, though.

Monday, January 5, 2009

Generating Java clients from WCF web service, trouble with JAXBElement

If you are generating a Java client from WCF service WSDL the result is usually a lot of JAXBElement types instead of simple or complex types defined in the service schema.

While there is nothing wrong with that result, you may want to get rid of most or all of JAXBElements and thus make your code smaller, cleaner and easier to use.

The first solution might seem to be using <xjc:simple/> configuration as shown in http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html.
It is usually not the case with WCF services and so the <xjc:simple/> method does not help.

The actual solution is found in https://jax-ws.dev.java.net/guide/Customizations_for_WCF_Service_WSDL.html.

Just use generateElementProperty="false".

Quote from the solution:
When processing a WCF service WSDL, it is recommended that the generateElementProperty customization be set to false in <jaxb:globalBindings>


The generateElementProperty attribute was introduced in JAXB 2.1.

Sunday, March 2, 2008

Linux shell one-liners

There is a great blog on shell one-liners and other linux topics: linux.dsplabs.com.au

One word of caution though: many of these one-liners use special characters, which many shells treat differently. If you use a visual shell like Midnight Commander then you will most certainly be in trouble (scripts WILL misbehave). Use bare bash to get better results.

There also have been locale related bugs in interpreters resulting in incorrect execution. When that happens, try experimenting with LC_ALL=C or LC_ALL=en_US.utf8 etc.

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!