torsdag den 7. juni 2018

SOLID principles, in layman's terms: Open/Closed

Raison d'être: I set out to write about the SOLID software development principles. Specifically, my aim was/is to make these things more understandable to other developers who, much in the way as yours truly, found it troublesome to have to decipher lengthy, complex articles and books on the matter. These principles are for everyone to learn and use, but I found that they were hard to fully grasp; quite possibly because I come from a non-English speaking background. So with this series of articles I'm setting out to try and de-mystify the principles, with only the best intentions in mind. The principles apply to many layers of software development. In my articles, I specifially aim to describe them as they relate to programming. I hope it'll be of use to you. Thank you for stopping by.

This will be a 5 article-series about SOLID. SOLID is all the rage; at least as far as the job-adds I'm reading are concerned; "you are expected to honor the SOLID principles" etc. So what exactly is SOLID about? Plain and simple, it's a set of guide-lines in developing object-oriented systems. They are a group of concepts that have proven themselves valuable for a great many people coding a great many pieces of software. A tale told by your elders in software engineering, if you will, that you will want to pay heed to so you can boast on your CV that you're into the SOLID principles - and you'll be a better developer for knowing them, I promise you that. Heck, you're a better developer for simply _wanting_ to know them!

S[O]LID - The Open/Closed principle

The open/closed principle is the second in the set of principles that make out the SOLID acronym. According to the wikipedia entry, it states that [quote] "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" [/quote]. What is basically comes down to is this, that you'll want to write your classes in such a way that you won't have to change them once they're done.

And why is this a good thing? Because changes to code is an inheritantly bad thing. Or, to put it differently, if you find yourself having to make changes to your source code when it's gone into production, that's when this principle would come in handy. Why, because source code once it's there and it's been tested, it's been reviewed, it's been released, by then it doesn't lend itself well to changes. Add a feature, tweak a business-rule, and you might find yourself in a situation where something, somewhere breaks, and you could be in a heap of trouble.

So what to do about it? It's easy: program to abstractions (i.e. interfaces or inherit from abstract classes), not implementations. When your variables are interface types, you won't need to specify until run-time which kind of implementation will actually represent the interface type. Consider this basic example:

First here's doing it wrong:

public class Logger
    {
        private DiskLogLocation diskLogLocation;

        public Logger()
        {
            diskLogLocation = new DiskLogLocation(@"c:\logs");
        }

        public void LogMessage(string message)
        {
            diskLogLocation.Log(message);

        }
    }


Above we have a Logger-class, which news up a 'DiskLogLocation', and write a message to it - supposedly to the directory specified in the constructor. The problem with this lies in the fact that we've now limited ourselves to the DiskLogLocation - we haven't fulfilled the open/closed principle, in as much as we cannot easily extend the Logger-class to allow us to log to the event-viewer, for example. Then here's doing it right:


 public class Logger
 {
        // use an interface, and provide an implementation of 
        // this interface at runtime
        private ILogLocation logLocation;

        public Logger()
        {
        }

        public void LogMessage(string message)
        {
            logLocation.Log(message);

        }
 }



In the above, the logLocation acts as a placeholder for which ever implementation of ILogLocation we eventually choose to use (see "As an aside" below on how to do just that). That's basically it - the open/closed principle in a nut-shell. That's - basically - what's meant by "open for extension": that we program to interfaces, not implementations, so that we can pull in whichever code we desire at a later time by simply replacing the interface implementation - for example, an 'EventLogger' class.

There're more ways than one to go about extending a class; the above is just one - but a fine place to start non the less, if I may say so myself. You can do magical stuff with some so-called 'design patterns', for example the strategy pattern, but that's for a different article.

So now you know about the open/closed principle: that you should program to interfaces and thus leave your options open for extention. Easy to learn, difficult to master; it takes some experience, recognizing the internal workings of a class that should be left open for extention. And there's always the risk of going overboard and interfacing to everything, when the business requirements just don't call for it. These discussions can get right religious at times.

______________

As an aside; how do you then, at run-time, specify which implementation will be used? Well one way is to have a method on your utilizing class which offers the possibility of exchanging the implementation with another one. For example, like so:

public class Logger
 {
        private ILogLocation logLocation;

        public Logger()
        {
        }
        
        public void SetLogLocation(ILogLocation _logLocation)
        {
            logLocation = _logLocation;
        }

        public void LogMessage(string message)
        {
            logLocation.Log(message);
        }
 }


Another way is to allow for this in the utilizing class' constructor, like so:

public class Logger
 {
        private readonly ILogLocation logLocation;

        // allow the Logger instantiator to determine which
        // implementation to use
        public Logger(ILogLocation _logLocationDeterminedAtRunTime)
        {
            logLocation = _logLocationDeterminedAtRunTime;          
        }

        public void LogMessage(string message)
        {
            logLocation.Log(message);

        }
 }

Given the above example you might possibly determine which implementing class to use via a configuration file, which you run at your composition root - which is just a fancy way of saying 'where your app starts', typically main() or ApplicationStart() or what have you. You could do this, for a simplistic example:

static void Main(string[] args)
        {
            string logLocationType = ConfigurationSettings.AppSettings.Get("type_of_log_location");
           
            if (logLocationType == "Disk")
            {
                logLocation = new DiskLogLocation(@"c:\logs");
            }

            Logger logger = new Logger(logLocation);
            logger.LogMessage("hello log");

        }

Here we're asking a configuration-setting for know-how on which type of log-location we wish to use, and we instantiate that pass it to the logger on construction. Now you're well on your way to using something fancy called 'Dependency Injection', - which in turn is just a fancy way of saying 'we are giving our classes the variables they need in their constructor*'. A topic for a different article entirely.

*) Usually the case, but not always.


Hope this helps you in your further career. And good luck with it, too!

Buy me a coffeeBuy me a coffee

Generating a Pie Chart with Grafana and MS SQL

Struggled with this, hope it helps someone.

In Grafana, if we base a visualization on an MS SQL query, and if we set Format as to Time series, for use in Graph panel for example, then the query must return a column named time.

This can make life difficult if we need to generate a chart, for example a Pie Chart, where we don't have a need for the time-visualization as with a bar-chart, for example.

A way around this is to return an aggregate 'time' column, like in the below:


SELECT sum(exceptionCounts.hasException), exceptionCounts.title as metric, count(exceptionCounts.time) as time
FROM
(
SELECT sessionnumber, 
        operationResult, 
        startdatetime as time
  FROM  foobarDatabase.dbo.fullSessionLog
  where operationResult like '%xception%' 
    AND $__timeFilter(customDateTimeStamp)
  group by sessionnumber, operationResult
) as exceptionCounts
group by exceptionCounts.title

In the above, we execute an inner query, which returns a timestamp. In the outer query we can then aggreate - 'count' - the timestamp. And the Pie Chart should work.

Took me some time and then some to figure that one out. Hope it helps!

Buy me a coffeeBuy me a coffee

mandag den 4. juni 2018

Using Docker to maintain a development environment

Would you contemplate using a containerized environment, that you might tear down at any moment, only to build it right back up again, indistinguishable from how it was before?

I have recently begun to do just that. And it has greatly enhanced my workflow, and productivity. And I’ll try and describe the how’s and why’s below.

When we think of Docker, we usually think of reasonably light-weight container instances, that we may assemble and congregate to form various applications. Each container yields usually a particular and highly individual service, for example a REST API or a database instance or a memory-cache or what have you, and we can compile these into low coupled solutions. And there’s the potential of scaling, of the low overhead and cost that comes with only keeping the containers running for the duration of their workload. That’s the norm. But really one of the fundamental aspects of using Docker is in how we can create a image of anything we wish, and the brevity - all things being equal - in how Docker image recipe makes it simple to conjure up the exact same environment to the detailed spec that is the Docker-file. Right now I’m running a number of only slightly different Ubuntu-based development environments in docker containers, that I access remotely. It’s based on an Ubuntu 17.10, it’s got Visual Studio Code installed, a few extensions to that, a browser, git. Postman, that I recommend for API testing.

Why would one wish to do that, 'dockerize' a full-fledged Ubuntu, weighing in at some odd gigabytes? I can think of several promoting arguments:

- The transparency of the environment. Setting up a dev machine can be tedious, and after a while it inevitably becomes cluttered with updates and remains of old installs and such. A Docker-initiated container won’t suffer from this; any changes are made in the Docker-file and we’ll get into the habit of tearing the machine down, and building it anew, soon enough. A Docker-container will never become polluted over time; it doesn’t live long.

- The versioning of Docker-files becomes a major factor. We can have several distinct Docker-files, each suited to their individual project. Some will hold a different set of tools than others. Also, significant, it won’t hold applications that aren’t needed for solving the particular problem you’re working; no e-mail app, no chat app, no office-app if that’s not required. It’s easy to not become distracted when the apps that might distract us aren’t installed.

- Sharing is caring. I can work on the exact same development environment as my colleague, as we’re both tied in to the same dev setup. I’ve so suffered in the past when for some reason I wasn’t able to access the same resources, or install the same apps, as my colleague. Everything is the same; it narrows the focus on the project. And when I need to hand the project over to the developer next to me, I can be sure the environment will work the way I intended.

- I needn’t worry about my machine breaking down. Not that this has happened many times during my career, but there has been the odd irrecoverable crash. With a Docker environment, I just need to get my hand on a machine, any damn machine that will run Docker. Pull the Docker image, spin up the container, get to work.

There are, of course, alternatives. I’ve often used Virtual Machines, that I’ve provisioned and enhanced with scripts, be it apt-get’s on Linux or Choco on Windows. But they’ve never worked for me in the same fashion as does Docker-containers. They too became polluted over time, or it was a hassle to take them on the road with me. Docker containers are easier; the fire-and-forget nature appeals to me. A virtual machine takes up a chunk of space that you’ll only get back when you stop using it; a Docker-container only takes up space for as long as you use it - though we do need to remember to clean up after ourselves.

It’s not all pros, there are some cons, of course there are. If I’m not disciplined enough to commit my development changes to source control, well, let’s just say that a Docker-container doesn’t have a long attention-span! But that really all I can hold against the notion.

The potential in tying in the development environment with project-specific software and -configuration is, I can testify, quite productivity enhancing - and I, for one, won’t be going back to maintaining my own development machine.

Enough talk, let’s try it out. Install the Docker community edition if you don’t have it already. Then take a glance at my template Docker-file, below, for an Ubuntu 17.10 with VS Code and a few related extensions installed, as well as a Firefox Browser, the Postman API development tool and a few other tools of my personal liking. It’s heavily annotated so I’d like to try and let the comments speak for themselves:

# Get the distro:
FROM ubuntu:17.10
ENV DEBIAN_FRONTEND=noninteractive

# Do some general updates.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* &&  apt-get clean &&  apt-get update -y &&  apt-get upgrade -y
RUN apt-get install -y software-properties-common 
RUN add-apt-repository universe
RUN apt-get install -y cups curl sudo libgconf2-4 iputils-ping libxss1 wget xdg-utils libpango1.0-0 fonts-liberation
RUN apt-get update -y && apt-get install -y software-properties-common && apt-get install -y locales 

# Let's add a user, so we have a chance to, well, actually use the machine.
ENV USER='thecoder'
ENV PASSWORD='password'
RUN groupadd -r $USER -g 433 \
    && useradd -u 431 -r -g $USER -d /home/$USER -s /bin/bash -c "$USER" $USER \
    && adduser $USER sudo \
    && mkdir /home/$USER \
    && chown -R $USER:$USER /home/$USER \
    && echo $USER':'$PASSWORD | chpasswd

# Let's add a super user, too. Same password
ENV SUDOUSER='theadmin'
ENV PASSWORD='password'
RUN groupadd $SUDOUSER \
    && useradd -r -g $SUDOUSER -d /home/$SUDOUSER -s /bin/bash -c "$SUDOUSER" $SUDOUSER \
    && adduser $SUDOUSER sudo \
    && mkdir /home/$SUDOUSER \
    && chown -R $SUDOUSER:$SUDOUSER /home/$SUDOUSER \
    && echo $SUDOUSER':'$PASSWORD | chpasswd

# Configure timezone and locale to en_US. __Change locale and timezone to whatever you want.__
 ENV LANG="en_US.UTF-8"
 ENV LANGUAGE=en_US
 RUN locale-gen en_US.UTF-8 && locale-gen en_US
 RUN echo "Europe/Copenhagen" > /etc/timezone && \
     apt-get install -y locales && \
     sed -i -e "s/# $LANG.*/$LANG.UTF-8 UTF-8/" /etc/locale.gen && \
     dpkg-reconfigure --frontend=noninteractive locales && \
     update-locale LANG=$LANG

# Create an keyboard-layout file, so we won't have to set it every time the machine starts. Just replace XKBLAYOUT="dk" with your layout, ex. "us".
# RUN printf '# Consult the keyboard(5) manual page.\nXKBMODEL="pc105"\nXKBLAYOUT="dk"\nXKBVARIANT=""\nXKBOPTIONS=""\nBACKSPACE="guess"\n'"" > /etc/default/keyboard
# - doesn't work :(
# set danish keyboard layout
# RUN setxkbmap dk  - doesnt work :(

# Install some much needed programs - nano, midnight commander, guake terminal
RUN apt-get install nano -y
RUN apt-get install mc -y
RUN apt-get install guake -y

# Use the Xfce desktop. Because it's nice to look at, in my opinion.
RUN apt-get update -y && \
    apt-get install -y xfce4
# There's also the MATE desktop-enviroment. Bit more light-weight.
#RUN apt-get update -y && \
#   apt-get install -y mate-desktop-environment-extras    

# Install git
RUN apt-get install -y git
# Install Python.
# Whoa, waitaminute - Ubuntu 17.10 already comes with Python 3.6 as default. Just run python3 to invoke it.

# Install Firefox
RUN apt-get install firefox -y

# Install Postman to 'opt' dir
RUN wget https://dl.pstmn.io/download/latest/linux64 -O postman.tar.gz
RUN tar -xzf postman.tar.gz -C /opt
RUN rm postman.tar.gz

# Install Visual Studio Code
ENV VSCODEPATH="https://go.microsoft.com/fwlink/?LinkID=760868"
RUN curl -fSL "${VSCODEPATH}" -o vscode.deb && dpkg -i vscode.deb

# To make it easier to automate and configure VS Code, it is possible to list, install, 
# and uninstall extensions from the command line. When identifying an extension, provide 
# the full name of the form publisher.extension, for example donjayamanne.python.
USER $USER
WORKDIR /home/$USER

# Enable viewing git log, file history, compare branches and commits - https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory
RUN code --install-extension donjayamanne.githistory
# Install Ms' python - linting, debugging, intellisense, etc.
RUN code --install-extension ms-python.python
# Install code outline provider - better code visualization in the explorer pane
RUN code --install-extension patrys.vscode-code-outline


# Annnnnd back to root for the remainder of this session.
USER root

# Install nomachine, the remote-desktop server that enables us to remote into the container image.
# You don't have to rely on my choice of NoMachine - just go to their website and get a different one, if you want.
ENV NOMACHINE_PACKAGE_NAME nomachine_6.1.6_9_amd64.deb
ENV NOMACHINE_MD5 00b7695404b798034f6a387cf62aba84

RUN curl -fSL "http://download.nomachine.com/download/6.1/Linux/${NOMACHINE_PACKAGE_NAME}" -o nomachine.deb \
&& echo "${NOMACHINE_MD5} *nomachine.deb" | md5sum -c - \
&& dpkg -i nomachine.deb


# Create an executable file that starts the NoMachine remote desktop server.
# A unix executable .sh-file must start with #!/bin/bash. '\n' means 'newline'.
# Note how the file ends with a /bin/bash-command. That's deliberate, it allows
# us do - don't ask me how - keep the container running when we use it later.
RUN printf '#!/bin/bash\n/etc/NX/nxserver --startup\n/bin/bash'"" > /etc/NX/nxserverStart.sh
# Now make the executable _actually_ executable ...
RUN chmod +x /etc/NX/nxserverStart.sh
# ... and start the nomachine-remote server when the container runs, and ...
CMD ["/etc/NX/nxserverStart.sh"]
#... happy developing! Use a NoMachine-client program to log into the server.
# PS: remember to run the container with the -d and -t arguments. 
# Check the readme.md file, https://github.com/harleydk/linuxRemoteDocker/blob/master/README.md

If you’re entirely new to Docker, hopefully the comments should make it clearer in what’s going on here. We start out by downloading a Linux distribution, then do a bunch of RUN commands which execute when we progress to build the image. This includes also setting up a user and a super-user. Save the file as ‘Dockerfile’, without extension, and execute, in the directory of this new Docker-recipe, the ...

docker build -t ‘linux_remote_pc’ .

... command. Then sit back and wait for a bit; the individual steps of the recipe will be executed and the image built. Now’s the time to put it to use. The image installs and runs a NoMachine service, enabling remote desktop connections via a NoMachine-client application. It’s fast and free. So go ahead and install a client from their web-site, https://www.nomachine.com/download, and spin up a Docker container from the Docker image just built, by issuing the following command:


docker run -d -t -p 4000:4000 --name=linux_remote_pc --cap-add=SYS_PTRACE linux_remote_pc_image

This will spin up an instance, a ‘Docker container’ as it's called, with the port 4000 of the Docker host mapped to port 4000 on the container - the port that we’ll connect to, as we remote access the container. So, on a machine on the same network as the Docker host, start the NoMachine client and connect:



...Where 'localhost' would of course be the host-name of the machine where the Docker-container is running. Having connected to the container, this should be the first that you see:



The machine tells us it lacks some basic configuration - that’s because we installed it unattended, so as not to have to deal initially with all those setup-choices. Just press ‘use default config’ and be done with it.

That’s it - you now have a machine with VS Code installed:





If you’re new to Docker there’re lots of tutorials and documentation available. Highly recommend trying build commands on a ‘hello world’ Dockerfile instead of the slightly more complex one, above. If just to get a feel for the command-line interface and command responses.

The Docker-file above is maintained at my github repository, https://github.com/harleydk/linuxRemoteDocker, which you can clone if you like. There’s a bit of further documentation available there.

I’d much like to hear from you, if you find it useful and/or if I can help out in some way. So drop me a note in the comments or create a new github issue, https://github.com/harleydk/linuxRemoteDocker/issues and I’ll see what I can do.

Thanks for reading & happy developing.


Buy me a coffeeBuy me a coffee

onsdag den 28. marts 2018

onsdag den 9. august 2017

Perform SOAP request with advanced headers in Informatica Powercenter Designer

My colleague had a problem in trying to access a SOAP web service. Specifically, he needed to provide extended SOAP headers in regards to the security requirements, a SAML assertion in this case, and the standard Informatica Web Transformation wasn't doing much for him.

We discovered that this is quite easy to accomplish by way of a Java Transformation instead. So here's how we did it.

In informatica, create the Java Transformation. Below illustration depicts it between two other transformations, and we can see how the input and output values are transferred between them.


Please pardon the use of the Danish language; I hope the gist of it will suffice.

Below picture further illustrates the input and output ports of the Java transformation. These exact names are immediately available to us from the Java code we write.


Now, let's add the Java-code specific to the task of calling a SOAP web-service with headers and all.

We have multiple tabs available on the transformation - I used only the 'On Input Row' and 'Import Packages'.

In 'Import Packages', specify the Java imports required to perform the task. For my use-case, these were sufficient:


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;


Given these imports, we can go ahead and add the Java code in the 'On Input Row' tab. Please forgive the wall of text; I felt it was important to include the complete SOAP request, headers and body and all, for the sake of completeness.



try {
 logInfo("Sending soap request");

 String FagsystemNavn = fagsystemnavn_Input; // corresponds with the input variable name
 String KundeNummer = kundenummer_Input;  // corresponds with the input variable name
 String MeddelelseTypeNummer = meddelelsetypenummer_Input;  // corresponds with the input variable name
 String KanalTypeNummer = kanaltypenummer_Input; // 1=mail, 2=sms, 4=brev
 Integer currentTimeInSeconds = (int) (System.currentTimeMillis() / 1000);
 String MeddelelseBatchId = currentTimeInSeconds.toString();

 String soapTemplate = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns=\"http://task.dk/begrebsmodel/2009/01/15/\">\n"
   + "   <soapenv:Header>\n"
   + "      <wsse:Security soapenv:mustUnderstand=\"1\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n"
   + "         <Assertion AssertionID=\"d913c046fddbca7a55fca34caf18cafa\" IssueInstant=\"2007-05-14T11:45:21.835Z\" Issuer=\"test.unsigned\" MajorVersion=\"1\" MinorVersion=\"1\" xmlns=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\">\n"
   + "            <Conditions NotBefore=\"2007-05-14T11:43:21.834Z\" NotOnOrAfter=\"2112-05-14T11:43:21.834Z\"/>\n"
   + "            <AuthenticationStatement AuthenticationInstant=\"2007-05-14T11:45:21.834Z\" AuthenticationMethod=\"urn:oasis:names:tc:SAML:1.0:am:unspecified\">\n"
   + "               <Subject>\n"
   + "                  <NameIdentifier Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\" NameQualifier=\"task\">taskGuid=wEFI04,ou=task,ou=internal,ou=entities,dc=task,dc=dk</NameIdentifier>\n"
   + "                  <SubjectConfirmation>\n"
   + "                     <ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:sender-vouches</ConfirmationMethod>\n"
   + "                  </SubjectConfirmation>\n" + "               </Subject>\n"
   + "            </AuthenticationStatement>\n" + "            <AttributeStatement>\n"
   + "               <Subject>\n"
   + "                  <NameIdentifier Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\" NameQualifier=\"task\">task=wEFI04,ou=task,ou=internal,ou=entities,dc=task,dc=dk</NameIdentifier>\n"
   + "                  <SubjectConfirmation>\n"
   + "                     <ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:sender-vouches</ConfirmationMethod>\n"
   + "                  </SubjectConfirmation>\n" + "               </Subject>\n"
   + "               <Attribute AttributeName=\"Groups\" AttributeNamespace=\"urn:bea:security:saml:groups\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
   + "                  <AttributeValue>PRIMARY_IDENTITY_Guid=14a2d120-bbda-4b62-aa27-1bbd8751f47f,ou=task,ou=internal,ou=entities,dc=fd,dc=dk</AttributeValue>\n"
   + "                  <AttributeValue>AUTH_LEVEL_6</AttributeValue>\n"
   + "                  <AttributeValue>EFfdfdfdbehandlerPRG</AttributeValue>\n"
   + "               </Attribute>\n" + "            </AttributeStatement>\n"
   + "         </Assertion>\n" + "      </wsse:Security>\n" + "   </soapenv:Header>\n"
   + "   <soapenv:Body>\n" + "      <ns:MeddelelseMultiSend_I>\n" + "         <ns:Kontekst>\n"
   + "            <tsk:HovedOplysninger xmlns:tsk=\"http://foobar.dk/begrebsmodel/xml/schemas/kontekst/2007/05/31/\">\n"
   + "               <tsk:TransaktionsID>A12345678ZZ</tsk:TransaktionsID>\n"
   + "               <tsk:TransaktionsTid>2010-10-14T13:30:47</tsk:TransaktionsTid>\n"
   + "            </tsk:HovedOplysninger>\n" + "         </ns:Kontekst>\n"
   + "         <ns:FagsystemNavn>${FagsystemNavn}</ns:FagsystemNavn>\n"
   + "         <ns:MeddelelseBatchID>${MeddelelseBatchId}</ns:MeddelelseBatchID>\n"
   + "         <ns:Meddelelser>\n" + "            <ns:Meddelelse>\n"
   + "               <ns:MeddelelseTypeNummer>${MeddelelseTypeNummer}</ns:MeddelelseTypeNummer>\n"
   + "               <ns:MeddelelseAfsenderReference>99</ns:MeddelelseAfsenderReference>\n"
   + "               <ns:KundeNummer>${KundeNummer}</ns:KundeNummer>\n"
   + "               <ns:MeddelelseOprettetAfMedarbejder>a5441</ns:MeddelelseOprettetAfMedarbejder>\n"
   + "               <ns:KanalTypeNummer>${KanalTypeNummer}</ns:KanalTypeNummer>\n"
   + "   <ns:KanalAdresseStruktur>\n" + "    <ns:AdresseValg>\n"
   + "     <ns:EmailAdresseStruktur>\n" + "      <ns:EmailAdresse>\n"
   + "       <ns:EmailAdresseEmail>anon@foobar.dk</ns:EmailAdresseEmail>\n"
   + "      </ns:EmailAdresse>\n" + "     </ns:EmailAdresseStruktur>\n"
   + "<!--\n" + "     <ns:TelefonNummerStruktur>\n"
   + "      <ns:TelefonNummerStruktur>\n"
   + "       <ns:TelefonNummer>888878787</ns:TelefonNummer>\n"
   + "      </ns:TelefonNummerStruktur>\n"
   + "     </ns:TelefonNummerStruktur>\n" + "-->\n" + "    </ns:AdresseValg>\n"
   + "   </ns:KanalAdresseStruktur>\n"
   + "               <ns:Filer/>\n" + "            </ns:Meddelelse>\n" + "         </ns:Meddelelser>\n"
   + "      </ns:MeddelelseMultiSend_I>\n" + "   </soapenv:Body>\n" + "</soapenv:Envelope>";

 String soapMessage = soapTemplate.replace("${FagsystemNavn}", FagsystemNavn)
   .replace("${MeddelelseBatchId}", MeddelelseBatchId)
   .replace("${MeddelelseTypeNummer}", MeddelelseTypeNummer).replace("${KundeNummer}", KundeNummer)
   .replace("${KanalTypeNummer}", KanalTypeNummer);

 URL url = new URL("http://the-url-to-your-soap-service");
 HttpURLConnection connection = (HttpURLConnection) url.openConnection();

 connection.setDoOutput(true);
 connection.setDoInput(true);
 connection.setRequestMethod("POST");
 connection.setRequestProperty("Content-Type", "text/xml");
 connection.setReadTimeout(5000);
 OutputStream out = connection.getOutputStream();
 Writer wout = new OutputStreamWriter(out);

 wout.write(soapMessage);
 wout.close();
 InputStreamReader isr = null;
 if (connection.getResponseCode() <= 400) {
  isr = new InputStreamReader(connection.getInputStream());
 } else {
  isr = new InputStreamReader(connection.getErrorStream());
 }

 BufferedReader in = new BufferedReader(isr);
 StringBuffer responseSoapMessage = new StringBuffer();
 String inputLine = in.readLine();

 while (inputLine != null) {

  // the response xml contains both the merged xml and the deploy logs.
  responseSoapMessage.append(inputLine);
  inputLine = in.readLine();
 }
 in.close();
 System.out.println("responseSoapMessage" + responseSoapMessage.toString());
 int webServiceKaldSucces_Output;
 
 if (responseSoapMessage.toString().contains(
   "<n1:AdvisNummer>1001</n1:AdvisNummer><n1:AdvisTekst>Request with 1 messages was acknowledged</n1:AdvisTekst>")) {
  webServiceKaldSucces_Output = 1; // succes in web-service call - stored in the output variable
  logInfo("Soap request - succes");
 } else {
  webServiceKaldSucces_Output = 0; // error in web-service call  - stored in the output variable
  logInfo("Soap request - error");
 }

 generateRow(); // Required by Informatica to generate output.
 
} 
catch (Exception ex) 
{
 logError(ex.getMessage());
}


The above code pieces a SOAP-request together, replacing specific values within it with the input-port values. Then, by way of a standard Java HttpURLConnection, it fires off the request, and reads the response into a StringBuilder object. For our use-case we report succes or failure by looking for a particular piece of String-value, we had no need to parse the response further.

Do note the call to 'generateRow()', this is required by Informatica to populate the output value(s).

Also note the calls to logInfo(message) and logError(message), respectively, these enable logging into the Informatica Session log. So by all means go crazy with them, you can never have enough logging.


Hope it helps!


Buy me a coffeeBuy me a coffee

torsdag den 8. december 2016

Using C# .NET for auto-responding to SurveyMonkey surveys


If you need to do auto-responding to SurveyMonkey surveys, you can perchance use the below C# code as a source of inspiration.

I used it for testing a survey that we had running internally. Please bear in mind that your company's SurveyMonkey-subscription might put a cap on the number of survey-responses.

Basically it works by utilizing the Selenium library (download it care of Nuget) for web-page testing. I used the Chrome web-driver because it didn't store cookies or history for the browser session. I also included a unique temporary value for when accessing the survey, or I would get the "you've already responded"-message.

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 58; i++)
            {
                RespondToSurvey();
                Console.WriteLine(i);
            }
        }

        private static void RespondToSurvey()
        {
            OpenQA.Selenium.Chrome.ChromeDriver driver = new OpenQA.Selenium.Chrome.ChromeDriver();

            string baseUrl = $@"https://da.surveymonkey.com/r/?tempValue=" + DateTime.Now.Ticks;

            driver.Navigate().GoToUrl(baseUrl + "/");

            Actions actions = new Actions(driver);
            IWebElement radioBtn = driver.FindElementById("72374030_573987619");
            actions.MoveToElement(radioBtn).Click().Perform();

            var element = driver.FindElement(By.Name("surveyForm"));
            element.Submit();

            driver.Close();
        }
    }

søndag den 4. december 2016

Connecting an ESP8266-12 to a DS1820 thermometer and do a http post data to the internet.

Here's how to connect an ESP8266-12 to a DS1820 thermometer and perform a

Pre-requisites! I'll be using these components:

@ A standard ESP8266-12 on a breakout-board such as the one shown.
@ Three 4.7k resistors.
@ A 3v battery (I use a CR123a) for powering the ESP826612.
@ A DS1820 (i use the DO-92 packaged version below)

The following requirements should be met:

@ All connections, including those on the The ESP 8266-12 on the breakout-board, are solid and conduct power as they should.
@ The battery holds at least 2.8v of power.
@ The ESP8266-12 has been programmed with the following sketch:



#include OneWire.h
#include DallasTemperature.h
#include ESP8266WiFi.h
#include ESP8266WiFiMulti.h
#include ESP8266HTTPClient.h

#define ONEWIRE_PIN 13

OneWire oneWire(ONEWIRE_PIN);
DallasTemperature sensors(&oneWire);

boolean TempSensorAvailable = false;
DeviceAddress TempSensor;
float tempCtry1;
float tempCtry2;

//AP definitions
#define AP_SSID "your wifi-network name here"
#define AP_PASSWORD "wifi password here"

void setup() {

//enable this to test from the arduino serial monitor
Serial.begin(74880);

sensors.begin();

Serial.print("Found ");
Serial.print(sensors.getDeviceCount(), DEC);
Serial.println(" OneWire device(s).");

// report parasite power requirements
Serial.print("Parasite power: ");
if (sensors.isParasitePowerMode()) Serial.println("ON");
else Serial.println("OFF");

if (!sensors.getAddress(TempSensor, 0)) {
Serial.println("No OneWire Device Found");
} else {
TempSensorAvailable = true;
Serial.println("OneWire Device Found");
sensors.setResolution(TempSensor, 12);
}
}

void loop() {

wifiConnect();
postTemperature();


delay(60 * 1000);
}

void postTemperature()
{
sensors.requestTemperatures(); // Get temprature
tempCtry1 = sensors.getTempC(TempSensor); // save temprature
sensors.requestTemperatures(); // Get temprature
tempCtry2 = sensors.getTempC(TempSensor); // save temprature

HTTPClient http;
http.begin("http://");
http.addHeader("Content-Type", "application/json");
String postMessagePart1 = String("{ 'sensorId' : 'L15-Out1', 'temperature' : '");
String postMessagePart2 = String("', 'postAttempts' : '");
String postMessagePart3 = String("', 'batteryVoltage' : '");
String postMessagePart4 = String("' }");
String postMessage = postMessagePart1 + ((tempCtry1+tempCtry2)/2) + postMessagePart2 + retries + postMessagePart3 + vdd + postMessagePart4 ;
int httpCode = http.POST(postMessage);
Serial.print("http result:");
Serial.println(httpCode);

http.writeToStream(&Serial);
http.end();

if ( httpCode != 200 && httpCode != 201)
{
delay(1000);
postTemperature();
}
}

void wifiConnect()
{
Serial.print("Connecting to AP");
WiFi.begin(AP_SSID, AP_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}

Serial.println("");
Serial.println("WiFi connected");
}



Don't forget to import the libraries into the arduino IDE environment.
Given the above is in order, go ahead and connect the components as per the following pictures:



- The RESET and GPIO16 pins should be connected - this enables the ESP 8266-12 to wake up from deep sleep mode.
- The GPIO0 and GPIO2 should be connected to VCC with a 4.7k resistor in the middle. This is to prevent a so-called 'zombie-mode', in which the ESP8266-12 has trouble waking up from deep sleep.
- The data-line and the VCC line of the DS1820 should be joined by a 4.7k resistor, or the temperature will not be read.
- The data-line of the DS1820 should be connected to the GPIO13-pin of the stand-alone ESP8266-12, as this corresponds with the "#define ONEWIRE_PIN 13" statement of the code.


With the ESP connected like so, my ESP8266-12 happily does a http post to my web-service every 60 seconds, before repeating the cycle.

You should put the ESP8266-12 into deep sleep mode if you power your thermometer via battery.