As my blog identity indicates, I am the .Net Ninja – self proclaimed, which is definitely a little weak… but how else do we form a web persona?
Outsiders may see the persona name, or company I work for, and assume that I only do .Net or work with Microsoft (GP) products; this is simply not the case. So naturally, when a client requests a project that is outside that box, I am intrigued. One or my recent project requirements can be simply stated: “We must integrate data to and from Great Plains using Java.”
The requirement is best met using GP web services, as interoperability and logistics are of the utmost importance. This was my recommendation to the client, which was met with some hesitation. I was surprised. I began to recall my early days as a developer – I spent 4-5 years working with Java. It was the first language I had ever used. My familiarity led to confidence – I proposed a proof of concept project, in which I would help build a sample Java client.
Googling this topic (GP Web Services and Java) does not turn up many positive results, largely because the Java and .Net communities do not seem to communicate effectively. My online research came up with many threads that said things like “I am a .Net developer…I do not know Java. Or, I am a Java developer…I do not know .Net”.
So, what do we really need? Someone who knows both (at least to some degree). Enter the .Net Ninja, who surprisingly knows Java…after all, Java made his transition to C# a breeze.
I built a simple Java Swing application (Image 1) in Eclipse that would provide data entry for standard GL Journal Entry transactions. I constucted business objects for use with the client; my purpose was to plug-in the Java Client Proxy after having all the other pieces in place…plus, it would be a good exercise in recollection.
GP2010 Web Services do not run out of IIS. Instead, they use the newer Service Host model and run directly out of Administrative Tools->Services. Additionally, Microsoft built two endpoints for connecting: Legacy and Standard. The Legacy endpoint basically boils down to the ASP.NET 2.0 implementation many are familiar with – basicHTTP binding and an .asmx file. The Standard endpoint uses wsHTTP binding and a WCF service -newer technology.
I ran into my first road block when I tried to generate the client proxy source using Eclipse. I tried connecting to both endpoints using the IDE interface – neither would connect. I always got an error regarding a naming conflict in the WSDL. Some research indicated that I could alter the web service config files to get by the error – no such luck. I should admit here, that a colleague was able to generate the proxy from Eclipse using the wsdl2java tool from the command line, but I am not aware of his environment, Eclipse version, or additional libraries used… I didn’t even bother to ask, as I already had a solution when he told me.
I decided to change my Java toolset…enter Project Tango, NetBeans, and Glassfish Metro. Project Tango is around for this specific purpose – interoperability between .Net web services and Java. After changing to the latest NetBeans tools (Metro included), I was able to generate the Client Proxy using just the interface…no command lines, no issues. I did bind to the Legacy endpoint, as opposed to the Standard endpoint. (Image 2)
With this hurdle out of the way, I was ready to start sending transactions to Great Plains 2010. I plugged in the remaing web services client logic, and immediately began sending transactions to Dynamics GP 2010 (Image 3).
Of course, I was running under administrative context – able to connect to GP as God and do anything I want. When I tested as a different user…failure.
This is where the important information comes into play: a little bit of knowledge about .Net services and Java Proxy Clients goes a long. NetBeans uses the JAX-WS toolset, which uses java.net for its protocols, authentication, and handshaking. The issue boils down to, “How can I progammatically impersonate a windows user account with java.net when connecting to GP2010 Legacy Web Services?” (Image 4)
The solution involves three components:
import java.net.Authenticator;
import java.net.PasswordAuthentication;
public class CustomAuthenticator extends Authenticator{
private String myUserName;
private String myPassword;
public CustomAuthenticator(String username,String password)
{
super();
myUserName = username;
myPassword = password;
}
@Override
protected PasswordAuthentication getPasswordAuthentication()
{
char[] pwdChar = myPassword.toCharArray();
return new PasswordAuthentication(myUserName,pwdChar);
}
}
import javax.xml.ws.BindingProvider;
import java.util.Map;
import java.net.URL;
import java.net.Authenticator;
protected void btnTestClick()
{
//My SuperUser credentials
String uName = “DEMO\Administrator”;
String uPass = “XYZ123”;
String oEndpoint = “”http://DEMO:48620/DynamicsGPWebServices/DynamicsGPService.asmx””;
//Create a CustomAuthenticator and establish that is the default
//java.net Authenticator
Authenticator.setDefault(new CustomAuthenticator(uName, uPass));
URL dgpWsdlUri;
String dgpuri = “http://DEMO:48620/Metadata/Legacy/Full/DynamicsGP.wsdl“;
try
{
dgpWsdlUri = new URL(dgpuri);
//Get the service interface and legacy endpoint
DynamicsGP_Service service = new DynamicsGP_Service(dgpWsdlUri);
System.out.println(“** Got DynamicsGP Service interface ***”);
DynamicsGP dynamicsGP = service.getLegacyDynamicsGP();
//Override the URL; I only put this in to show how.
BindingProvider bp = (BindingProvider)dynamicsGP;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, oEndpoint);
//Override default BindingProvide information
Map<String, Object> map = bp.getRequestContext();
map.put(BindingProvider.USERNAME_PROPERTY, uName);
map.put(BindingProvider.PASSWORD_PROPERTY, uPass);
//Begin Web Services Work
//……
//End Web Services Work
} catch (Exception e)
{
//Handle Exceptions
}
}
<?xml version=”1.0″ encoding=”utf-8″?>
<bindings>
<basicHttpBinding>
<binding name=”BasicHttpBindingTarget”>
<readerQuotas maxDepth=”2147483647″ maxStringContentLength=”2147483647″ maxArrayLength=”2147483647″ maxBytesPerRead=”2147483647″ maxNameTableCharCount=”2147483647″/>
<security mode=”TransportCredentialOnly”>
<transport clientCredentialType=”Digest”/>
</security>
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name=”WSHttpBindingTarget” maxBufferPoolSize=”524288″ maxReceivedMessageSize=”128896″ messageEncoding=”Text” textEncoding=”utf-8″ useDefaultWebProxy=”true”>
<security mode=”Message”>
<message clientCredentialType=”Windows”/>
</security>
</binding>
</wsHttpBinding>
<customBinding>
<binding name=”CustomBinding”>
<textMessageEncoding>
<readerQuotas maxDepth=”2147483647″ maxStringContentLength=”2147483647″ maxArrayLength=”2147483647″ maxBytesPerRead=”2147483647″ maxNameTableCharCount=”2147483647″/>
</textMessageEncoding>
<httpTransport maxBufferPoolSize=”2147483647″ maxReceivedMessageSize=”2147483647″ maxBufferSize=”2147483647″/>
</binding>
</customBinding>
</bindings>
Remember to place this config file in the correct location, and then restart the services using Administrative Tools->Services before trying to connect.
Hopefully, this article will help people that come across this “niche” situation, which should help prevent un-necessary hair loss and cursing. There are many things not discussed here (how to use Proxy objects, lower security layers, firewalls, etc.), but this solution should get many people off the ground and running.
“Those who realize their folly are not true fools.” – Chuang Tzu