I've been developing software with .NET professionally for the last five years or so, and aside from the occasional foray into other languages I've more or less specialized in that environment. While merrily hacking away at our back-end here at Semingo, we've recently made the decision to develop an aspect of said back-end in Java. As it's always a good practice to keep an open mind and experiment with other technologies I've happily accepted the challenge. After working with Java and its associated tools for the past three or so weeks I have several observations to make: - The prominent free Java IDE, Eclipse, is actually a very full featured and impressive platform but takes a lot of getting used to. Some of the idioms and concepts are radically different than Visual Studio; for instance, whereas in Visual Studio you'd create an "ASP.NET Application Project", in Eclipse you create (or convert to) a dynamic web project and then add something called facet to your project; for instance, a Dynamic Web Module facet allows you to easily create and debug servlets, and the "Axis2 Web Services Core" facet allows you to create Axis2-based web services and work on them from within your IDE. To actually make use of these features, however, one needs to develop a pretty hefty knowledge base on the various technologies involved (J2EE and servlets, servlet containers like Tomcat, WTP etc.)
- Eclipse is next-to-useless without some tinkering; in particular, what I originally attributed to very immature web development plug-ins - the WTP umbrella project I already mentioned - turned out to be the default memory settings of the Eclipse launcher. The launcher hosts the Java VM and its baseline configuration is simply inadequate. In my case adding the following switches: -vmargs -Xmx512M -XX:MaxPermSize=128M to the command line resolved all of the problems I had with the various WTP plug-ins, as well as the myriad crashes I've experienced with the IDE. In fact it's rock-stable now.
- The Java language has some unexpected caveats; for instance, whereas in C# the designers eschewed fall through in the switch statement (you can group labels to implementations, but you cannot fall through from the implementation of one case statement to the next), the Java designers elected to maintain C-style behavior. I'm of the belief that switch statement fall through is the cause of a huge number of subtle, hard-to-find bugs, and was surprised to learn of this discrepancy between the two languages.
- Enumerations in Java, a relatively new feature added in 1.5, are an impressively diverse feature which is a great deal more powerful than its C# counterpart. I only wish the designers would also allow for a more simplified "SOME_CONSTANT = 3" type syntax, as it's somewhat cumbersome to have to actually use constructors for the purpose. Additionally Java does not (to my knowledge) support implicit conversion operators, which makes necessary constructs such as SomeEnum.CONSTANT_VALUE.getConvertedValue(). It's not a huge issue but it's one of the many areas where syntactic sugar in C# is useful.
- Speaking of syntactic sugar, there're several aspects where Java simply falls short of C#: disposables, iterators and delegates. Yes, I know delegates are an essentially religious issue for the Java designers (mostly for historical reasons, I suspect), and I won't deny that anything you can do with delegates you can do with nested classes, but at ridiculous verbosity. As for disposables, I find that the using keyword in C# is one of the most useful language constructs I've ever encountered, the use of which goes way beyond the original intention of elegantly scoping unmanaged resource use; finally, iterators are tremendously useful and cut a lot of unnecessary boilerplate code out of the equation.
- The Java ecosystem is riddled with code- and buzz-words, to the point of being annoying. If you thought .NET has too many sub-technologies and acronyms, you should try Java. Just to get the taste buds going, here are some of the keywords I've been messing with for the past couple of weeks: J2SE, J2EE, Servlet, Eclipse, WTP, (Apache) Tomcat, Axis2, SAX, JAXP, JAX-RPC, JAR, WAR, AAR, EAR, JDBC, JavaDoc, JSP and JavaBeans. And that's just off the top of my head! To someone with any sort of Java experience this list wouldn't seem intimidating or even exhaustive, but to a new-comer that's simply too much. There is also an import cultural distinction: Visual Studio and its associated technologies (.NET, ASP.NET, ADO.NET etc.) are designed to ease you in as you learn the ropes; I found it much easier to simply start working with them and learn as I go, whereas with the Java counterparts I usually found myself trying to rework code samples found on the 'net while scratching my head.
Now don't misunderstand me: the Java technologies are generally impressive, mature and usable, but the learning curve is not nearly as comfortable as the competing technologies from Microsoft, and the tools and documentation just aren't as polished.
The Windows SDK command shell, setenv.cmd, is immensely useful, so much so that I wanted it as my default command prompt (i.e. when CMD is run, no matter by whom). A quick Google search didn't turn out anything, so I eventually figured it out myself. The trick is to add it to the command processor's AutoRun value in the registry (run cmd /? from the command prompt if you don't know what I'm talking about): reg add "HKCU\Software\Microsoft\Command Processor" /v AutoRun /f /t REG_EXPAND_SZ /d "\"%programfiles%\Microsoft SDKs\Windows\v6.0\Bin\SetEnv.Cmd\" /debug /x86 /vista" You'll notice that I explicitly set the arguments for setenv.cmd; I can't explain it (nor bothered to delve into the script), but without these arguments the script gets stuck along with the command prompt. You should obviously change the values to your own environment.
Our test code makes extensive use of a lightweight web server implementation based on the .NET 2.0 HttpListener class (I'll write a separate post on this, source code included, in the near future). The web server implementation randomizes an incoming port and exposes its URI via a property; this is done for two reasons: to avoid conflicts with other listeners on the machine, and to facilitate multi-threading test runners (like TestDriven.NET). Even with a serial test-runner (like ReSharper's unit test driver, which is what most of us at Semingo use) this means that for an average test suite a large number of web-server instances are initiated and disposed of in very rapid succession. Our implementation creates each web server on its own dedicated port (using Socket.Bind to make sure that the port is free), and closes the listener via HttpListener.Abort when the test ends. Although the entire test suite seems to run fine locally (both via ReSharper's test runner and using NUnit-Console), at some point we started getting strange errors from our continuous integration server: System.Net.HttpListenerException : Failed to listen on prefix 'http://+:40275/' because it conflicts with an existing registration on the machine. I verified this by running the tests with NUnit-Console on the CI server and it was consistent (but only on that server). Since the only instances of HttpListener used throughout the test suite are those used by the test web servers, this means that HttpListener.Abort does not properly unregister its own prefixes. Since the documentation for HttpListener is rather sparse and I couldn't find any mention of this issue on the web, I eventually went the Reflector route. Check out the Reflector decompiler output for both HttpListener.Dispose (called via Close) and HttpListener.Abort methods: HttpListener.Dispose | HttpListener.Abort | if (this.m_State != State.Closed)
{
this.Stop();
this.m_RequestHandleBound = false;
this.m_State = State.Closed;
} |
if (this.m_RequestQueueHandle != null)
{
this.m_RequestQueueHandle.Abort();
}
this.m_RequestHandleBound = false;
this.m_State = State.Closed; |
The primary difference is the call to HttpListener.Stop. Here's a code snippet from that method:
if (this.m_State != State.Stopped)
{
this.RemoveAll(false);
this.m_RequestQueueHandle.Close();
this.m_RequestHandleBound = false;
this.m_State = State.Stopped;
this.ClearDigestCache();
}
I'll spare you the hunt and point the problem out. There are two tangible differences between the two calls:
- HttpListener.Close closes the request queue handle (which is used in calls to the native HTTP API) whereas HttpListener.Abort aborts it. I didn't delve into this but the semantics seem to be the same as for the HttpListener itself.
- HttpListener.Close calls RemoveAll before disposing of the queue handle, presumably in order to stop accepting incoming requests.
In order to solve the problem, you can either remove the prefixes manually, or call the internal method like so:
listener.GetType().InvokeMember( "RemoveAll",
BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance,
null, listener, new object[] { false } );
It's actually pretty easy; get your web service up and running in axis, run svcutil.exe http://someserver/someurl/someservice?wsdl and you're good to go. Unless, of course, you're trying to return arrays from the Java side (primitive arrays at least, I didn't see much of a point testing with custom classes). Consider the following contract: public interface ServiceInterface {
int[] doSomething( String someParameter );
}
The generated WSDL looks something like this (edited for clarity... I hope):
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<wsdl:types>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="ArrayOf_xsd_int">
<complexContent>
<restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:int[]"/>
</restriction>
</complexContent>
</complexType>
</wsdl:types>
<wsdl:message name="doSomethingResponse">
<wsdl:part name="doSomethingReturn" type="impl:ArrayOf_xsd_int"/>
</wsdl:message>
<wsdl:message name="doSomethingRequest">
<wsdl:part name="someParameter" type="soapenc:string"/>
</wsdl:message>
<wsdl:portType name="ServiceInterfaceService">
<wsdl:operation name="doSomething" parameterOrder="someParameter">
<wsdl:input message="impl:doSomethingRequest" name="doSomethingRequest"/>
<wsdl:output message="impl:doSomethingResponse" name="doSomethingResponse"/>
</wsdl:operation>
</wsdl:portType> <!-- More uninteresting stuff -->
</wsdl:definitions>
The WDSL is perfectly fine and is properly processed by Microsoft's svcutil.exe tool; the generated classes look and appear to function normally, but if you'll look at the deserialized array on the client (.NET/WCF) side you'll find that the array has been deserialized incorrectly, and all values in the array are 0. You'll have to manually look at the SOAP response returned by Axis to figure out what's wrong; here's a sample response (again, edited for clarity):
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/>
<soapenv:Body>
<doSomethingResponse>
<doSomethingReturn>
<doSomethingReturn href="#id0"/>
<doSomethingReturn href="#id1"/>
<doSomethingReturn href="#id2"/>
<doSomethingReturn href="#id3"/>
<doSomethingReturn href="#id4"/>
</doSomethingReturn>
</doSomethingResponse>
<multiRef id="id4">5</multiRef>
<multiRef id="id3">4</multiRef>
<multiRef id="id2">3</multiRef>
<multiRef id="id1">2</multiRef>
<multiRef id="id0">1</multiRef>
</soapenv:Body>
</soapenv:Envelope>
You'll notice that Axis does not generate values directly in the returned element, but instead references external elements for values. This might make sense when there are many references to relatively few discrete values, but whatever the case this is not properly handled by the WCF basicHttpBinding provider (and reportedly by gSOAP and classic .NET web references as well).
It took me a while to find a solution (after which I stumbled onto this post, which wasn't trivial to find): edit your Axis deployment's server-config.wsdd file and find the following parameter:
<parameter name="sendMultiRefs" value="true"/>
Change it to false, then redeploy via the command line, which looks (under Windows) something like this:
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl
The web service's response should now be deserializable by your .NET client.
If you have a WCF service that returns a stream to the caller, you should be extra-careful with the source of these streams. In my case I was redirecting a file stream over a MessageContract that looks something like this: [MessageContract]
public class ItemInfo
{ ... [MessageBodyMember]
public Stream ItemStream;
}
It took one of the integration tests to fail... oddly to figure out that the stream wasn't getting disposed of after the operation was completed. The solution appears to be fairly simple, add the following lines to your service wrapper (if your concrete implementation is WCF-aware you can always just stick this straight in the implementation code): // Make sure stream gets disposed at the end of the operation
OperationContext.Current.OperationCompleted += delegate { item.ItemStream.Dispose(); };
Installing Microsoft Team Foundation Server is a ridiculously arduous and difficult process. I'll spare you my own complaints and simply list the checklist for installing this beast. This assumes you're installing TFS in a domain-enabled environment and in single-server mode; this is the typical configuration for a small-to-medium-size organization: - Designate a machine to host your Team Foundation Server repository. This machine must not double as a domain controller as this configuration is not supported by TFS.
- Set up at two regular user accounts (not administrators, and if you have any group policies you may - according to your configuration - want to keep these users out of the relevant OUs) in your Active Directory. I used the trivial TFSService and TFSReports accounts. Also you'll need a user with administrative privileges on the target server; I personally prefer to avoid the associated headaches, so I simply used a domain administrator user for installation purposes (but used the aforementioned two users to set the beast up).
- If necessary, install Windows 2003 Server (whatever flavor) on the machine; don't forget the necessary service packs and updates. If your pipe is fat enough, just let Windows Update do its magic.
- Add an Application Server role, make sure you enable ASP.NET 2.0 during the installation process
- Install SQL Server 2005. Make sure you read the installation guide first though, as you'll need to set it up to "Use the built-in System account," enable all services except Notification and finally select Windows authentication as the preferred authentication mechanism. You'll also need to let the SQL Server installer install a bunch of prerequisites before actual installation begins.
- Install SQL Server 2005 Service Pack 2.
- From the TFS installation media, install hot-fix 913393 for .NET Framework.
- Install Windows SharePoint Services 2.0 with Service Pack 2.0. Make sure you select server farm mode when installing, or you'll just have to redo the installation.
- Install Team Foundation Server itself.
- Back up the reporting services encryption key (you can find a description of the procedure here).
- Install hot-fix 919156, a.k.a the Quiescence GDR (no, I have no idea what GDR stands for).
- Install Team Foundation Server Service Pack 1.
- Make sure TCP port 8090 is open in your firewall software if you want web access to your Team Foundation Server (to be honest, I haven't found any use for it yet.)
- Install Team Explorer from the installation media (required for many add-ons, including eScrum).
- Install Visual Studio 2005 Team Suite Service Pack 1. This can, and will, take forever.
If at this point you're not thoroughly exhausted, you might want to set yourself up a with a project. We're currently evaluating the Microsoft eScrum template for our purposes; my colleague Oren Ellenbogen, in his capacity as Scrum Master, will probably be posting his thoughts on eScrum as a platform. In the meantime here's a quick list of solutions to problems we've encountered while configuring the beast: - Make sure you install the various prerequisites; in this case, .NET Framework 2.0, IIS, TFS and Team Explorer, AJAX Extensions 1.0 and the Anti-Cross Site Scripting Library
- At this point you're liable to get a strange SharePoint-related error if you try and create an eScrum-based project; if that's the case (or as a preemptive measure), just run iisreset on the TFS server.
- If you can't seem to access the eScrum website (nominally at http://yourserver/eScrum) you may have to reconfigure the eScrumAppPool identity from the IIS manager (right click the application pool, chose Properties, go to the Identity tab and enter the right information under Configurable)
- You may also get 404 errors from the eScrum website even though it's very obviously configured. We've found that the solution described here works as well:
- From the command prompt, type cd "%ProgramFiles%\Common Files\Microsoft Shared\Web Server Extensions\60\BIN"
- Run STSADM.EXE -o addpath -url http://localhost/eScrum -type exclusion
- Run iisreset again
- eScrum reports only update once every 1 hour. If this bothers you, follow the instructions here to reduce the lag.
Hope this saves someone out there a lot of time and headache (and if so, a comment or e-mail is always appreciated...)
A new organization and a new office require a new IT infrastructure. I'll spare you the gritty details and just mention several installation issues we've encountered and how to resolve them. | Problem: | Windows Server 2003 installed on a RAID array via NVidia RAID controller; drivers are not available on the regular installation CD-ROM. | | Solution: | Use a USB floppy drive (most servers and workstations don't come with floppy drives nowadays, and with good reason). In the case of the Tyan server we use, booting off of the driver CD allowed me to create a driver floppy which I then used when installing Windows. | | | | | Problem: | Windows can't locate file "nvraid.cat" when using the NVidia RAID controller drivers. | | Solution: | Hit the escape button to ignore the missing files (apparently that .CAT files are used for cryptographic purposes and are not provided by NVidia, see here). | | | | | Problem: | Nero refuses to install without installing DirectX 9.0c first on Windows Vista. | | Solution: | Although Vista comes with DirectX 10, you can safely agree to this and skip the DirectX installation later. Why a CD/DVD burning package requires DirectX (even though I didn't install any of the additional crap provided by Nero) is beyond me. | | | | | Problem: | Not strictly an installation issue, but there isn't any immediately apparent way to echo an empty line from the Windows command processor. | | Solution: | While "echo /?" won't tell you this, the MSDN documentation for the same command clearly mentions "echo." (written exactly so). Contrived. | | | | | Problem: | Setting up a DNS canonical name (alias) entry for a file server results in "Duplicate name exists on the network." errors when attempting to use the network resource. | | Solution: | Find registry key "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters" and set the value of "DisableStrictNameChecking" to 1. (More information) | | | | | Problem: | Newly configured DHCP server doesn't respond to "ipconfig /renew" requests. | | Solution: | Although I can't explain this, the problem was not the DHCP server; right-clicking the network connection icon and then selecting Repair did actually manage to obtain a lease. If this happens to you, don't get upset (as I did), try this technique first and see if it works for you. | I'll add a separate post on TFS and eScrum later today or tomorrow (hopefully).
I didn't want to post this before all formalities were complete, and now that they are I can finally announce my new workplace: a shiny new Israeli startup called Semingo. I can't currently say much about what it is we're doing, but it's a huge challenge and it's quite a change of pace from my previous job. It's also a nice plus working alongside fellow bloggers Oren Ellenbogen and Pasha Bitz, and trying out New and Improved™ methodologies such as Scrum. As an aside, here's a couple of videos of the last project I worked on running on the eMobile EM-ONE device (the second video appears to be a leaked beta version. Curious!) If you can read Japanese, there's more about it here. There's absolutely no way to demonstrate this without one of these devices, so while I regret the poor video quality, it's definitely better than nothing... Update: Now that we've enjoyed a bit of spotlight in Israeli publication The Marker (Hebrew only, sorry) I'll be able to progressively discuss more about what we do at Semingo. Heads up!
Imagine that you work mostly with C#. Imagine that on occasion you sprinkle SQL, Java and JavaScript into your daily routine, and that you also use the command line a lot. Can you see the problem? Didn't think so. I'll clue you in: the last one in that list has one distinct difference with the first four. Look around you. Have you found what we're looking for? That is correct. The answer is: the semicolon. A lightweight, generally meaningless way to end a statement in most common languages. Arguably useless to users, arguably beneficial to parsers, and the cause of almost half an hour of continuous head-scratching by both programmer and DBA. How so? C:\Temp>mysql -C -usomeuser -phispassword thedatabase; ERROR 1044 (42000): Access denied for user 'someuser'@'%' to database 'thedatabase;' I'll let you draw your own conclusions.
|