RSS feed for blog Linkin Skype Mail Me Twitter

Stickfight

Blog Category: software

Adding a member to IBM Connections communities programmatically

Sometimes I get the feeling the powers at be are not giving the IBM developers quite the time they need to document stuff, IBM Connections is the best example of it I know, take this sterling example of how to add a member programmatically and even if they have slightly better versions such as this they are not a patch on the notes ones, so every time I hit one and end up solving it my self I thought I would do a proper guide

Note: all of my documentation assumes the worst case scenario that I can think off which in this case is to have the “emails not visible” setting on, which means the Atom feeds will NOT accept email address as parameters, if you need to get the userid for Atom is this is the case you can get it with this function

The Atom ‘add user’ and search functions detailed later CAN take an email address, but as IBM always give you best case scenarios, I’m giving you a worst case one.

Oh, shout out to Mikkel Flindt Heisterberg at http://lekkimworld.com/ who is the connections god, and pointed me in the right direction when I was having a head bashing moment on a custom names space issue for this.

Right so we want to add a user to a restricted community in IBM connections and we want to do this via the atoms feeds, the minimum XML I have found that you need to add a new user to a community is this

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:snx="http://www.ibm.com/xmlns/prod/sn">                                   
    <contributor>                                                             
        <snx:userid xmlns:snx="http://www.ibm.com/xmlns/prod/sn">A5B8C712-27E5-596C-8625-7AC4000743C3</snx:userid>                                     
    </contributor>                                                                                                                               
</entry> 


It assumes you are adding them as a member, you submit this to the community member list url, which will be a bit like this

http://www.connectionssite.com/communities/service/atom/community/members?communityUuid=XXXXXXXXXXXX

where XXXXXXXXXXXX is the community ID which will be a string like this “c1l2a2e6-2e72-4a25-9a2f-l76e046a0d53”.

OK then we need a function to do this

 private String addUserAsAMemberofThisCommunity(String CommunityUnid, String adminUserName, String adminPassword, String baseURL, String memberlistURL, String UserIDtoAdd) throws NotesException {
        
        //Example parameters
        CommunityUnid = "c1l2a2e6-2e72-4a25-9a2f-l76e046a0d53"; 
        adminUserName = "Admin";
        adminPassword = "password";
        baseURL = "http://www.myconnections.com";
        memberlistURL = "/communities/service/atom/community/members?communityUuid="; 
        UserIDtoAdd = "A7B2C512-27E5-596C-8625-7AD4000843Z3";
        
        String ErrorReturn = "";
        try {
            //just incase some has passed the default user name for an Anonymous user
            if (!UserIDtoAdd.equals("Anonymous")) {

                Abdera abdera = new Abdera();
                AbderaClient client = new AbderaClient(abdera);
                AbderaClient.registerTrustManager();
                client.addCredentials(baseURL, null, null, new UsernamePasswordCredentials(adminUserName, adminPassword));

                Entry entry = abdera.newEntry();

                //thanks to Mikkel Flindt Heisterberg for telling me about this as i was doing it wrong
                javax.xml.namespace.QName contributornamespace = new QName("http://www.w3.org/2005/Atom", "contributor", "");
                javax.xml.namespace.QName useridnamespace = new QName("http://www.ibm.com/xmlns/prod/sn", "userid", "snx");

                ExtensibleElement contributorElement = entry.addExtension(contributornamespace);
                ExtensibleElement useridElement = contributorElement.addExtension(useridnamespace);
                useridElement.setText(UserIDtoAdd);

                ClientResponse response = client.post(baseURL + memberlistURL + CommunityUnid, entry);

                if (response.getStatus() != HttpURLConnection.HTTP_UNAUTHORIZED) {
                    ErrorReturn = "The User Id you are useing is noth athorised to do this";
                } else if (response.getStatus() != HttpURLConnection.HTTP_MOVED_TEMP) {
                        ErrorReturn = "You are most likly using a none ssl url to do this and connections wants a ssl link";
                } else if (response.getStatus() != HttpURLConnection.HTTP_CREATED && response.getStatus() != HttpURLConnection.HTTP_CONFLICT) {
                    ErrorReturn = "We could not add the user to the community automatically, it returned a status of: " + response.getStatus();
                }
            }

        } catch (ClassCastException e) {
            ErrorReturn = "Unknown Error: most likly the web service failed to respond";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ErrorReturn;
    }


Well that works just fine, job done right?

yeahhhhh…..not quite

In Connections, if a restricted community is a subgroup of another community, you have to add the user to all the sub communities before you can add them to this one (RUDE WORD)

so we have to work out how to see if a community has a parent community, which we can do with this happy function

 private String getParentLink(String CommunityUnid, String baseURL, String adminUserName, String adminPassword, String communityInstance) throws NotesException {
        
        //Example parameters
        CommunityUnid = "c1l2a2e6-2e72-4a25-9a2f-l76e046a0d53"; 
        adminUserName = "Admin";
        adminPassword = "password";
        baseURL = "http://www.myconnections.com";
        communityInstance = "/communities/service/atom/community/instance?communityUuid="; 
            
        String parentLink = "";
        try {
            URL feedUrl = new URL(baseURL + communityInstance + CommunityUnid);
            Abdera abdera = new Abdera();
            Parser parser = abdera.getParser();
            XPath xpath = abdera.getXPath();
            AbderaClient client = new AbderaClient(abdera);
            AbderaClient.registerTrustManager();
            client.addCredentials(baseURL, null, null, new UsernamePasswordCredentials(adminUserName, adminPassword));

            ClientResponse resp = client.get(baseURL + communityInstance + CommunityUnid);
            org.apache.abdera.model.Document<Feed> doc = resp.getDocument();

            //this is not a normal entry based document it is a FOMEntry, so we have to caste it to that
            FOMEntry feed = (FOMEntry) doc.getRoot();

            //and we are not after a atom item we are after a link in the xml document this link is identified by its 'rel' attibute
            if (null != feed.getLink("http://www.ibm.com/xmlns/prod/sn/parentcommunity") || feed.getLink("http://www.ibm.com/xmlns/prod/sn/parentcommunity").getHref().toString().equals("")) {
                parentLink = feed.getLink("http://www.ibm.com/xmlns/prod/sn/parentcommunity").getHref().toString();
                HashMap restrictedParm = getURLParameters(parentLink);
                parentLink = restrictedParm.get("communityUuid").toString();
            }

        } catch (Exception e) {
            //e.printStackTrace();
        }
        return parentLink;
    }


We can then gather this all together into one function that will do the check for nested communities and add the user to all of them in the right order

 private String addtoAllSubCommunites(String CommunityUnid, String adminUserName, String adminPassword, String baseURL, String memberlistURL, String UserIDtoAdd, String communityInstance) throws NotesException {
        //Example parameters
        CommunityUnid = "c1l2a2e6-2e72-4a25-9a2f-l76e046a0d53"; 
        adminUserName = "Admin";
        adminPassword = "password";
        baseURL = "http://www.myconnections.com";
        memberlistURL = "/communities/service/atom/community/members?communityUuid="; 
        UserIDtoAdd = "A7B2C512-27E5-596C-8625-7AD4000843Z3";
        communityInstance = "/communities/service/atom/community/instance?communityUuid="; 
        
        String ErrorReturn = "";
        try {

            List communityList = new ArrayList();
            communityList.add(CommunityUnid);
            //lets see if this community is a member of another community
            String parentCommunityUnid = getParentLink(CommunityUnid, baseURL, adminUserName, adminPassword, communityInstance);

            if (parentCommunityUnid.equals("")) {
                ErrorReturn = addUserAsAMemberofThisCommunity(CommunityUnid, adminUserName, adminPassword, baseURL, memberlistURL, UserIDtoAdd);
            } else {

                //in case there are multiple nested communities, lets recurse down and get them all
                while (!parentCommunityUnid.equals("")) {
                    communityList.add(parentCommunityUnid);
                    parentCommunityUnid = getParentLink(parentCommunityUnid, baseURL, adminUserName, adminPassword, communityInstance);
                }

                //reverse the list of parent communities as we have to add the from the root upwards
                Collections.reverse(communityList);

                //now we can finaly add the user as a member of all the communities in the right order
                for (int i = 0; i < communityList.size(); i++) {
                    String tempparunid = communityList.get(i).toString();
                    ErrorReturn = addUserAsAMemberofThisCommunity(tempparunid, adminUserName, adminPassword, baseURL, memberlistURL, UserIDtoAdd);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return ErrorReturn;
    }


There you go, I don’t know about you but it seems a lot of work just to add a user.

hope it helps.

Getting The User ID From IBM Connections - Addendum 1

As a little add on from This post on getting The User Id from IBM Connections, we discovered another little wrinkle, and that is the single sign on name that you get from @username in domino is not necessary the same as the what IBM connections is wanting in the ?Name= search parameter in its profile search, as we all should know the domino full name is not always the FirstName + ” ” + Last name then we tend to expect, so I just do a little lookup before running the function in the previous blog post

That is

String userid = convertSSOnametoFullName( userNab, agentContext.getEffectiveUserName().toString());
userid = getUserID( adminUserName, adminPassword, baseURL, ProfileNameSearch, userid);


The top function is below, the second function is here

private String convertSSOnametoFullName(  String NabNsf, String SSOUser) throws NotesException {
    System.out.println("convertSSOnametoFullName 0.1 " + SSOUser + NabNsf);
    
        String FullName = "";
        
        Session session = getSession();
        Database db = session.getDatabase(null, NabNsf);
        View view = db.getView("($VIMPeople)");
        DocumentCollection dc = view.getAllDocumentsByKey(SSOUser, true);
        //if we get more that 1 result then its a bad name to search and for security reasons we will return nothing
        if( dc.getCount() == 1) {
            Document nabdocument = dc.getFirstDocument();
            FullName = nabdocument.getItemValueString("FirstName") + " " + nabdocument.getItemValueString("LastName"); 
        } else if (dc.getCount() == 0){
            //we cant find that person in this address book so I going to return the name they put in as opposed to a blank, in case its a valid name in another address book
            FullName = SSOUser;
        }

        System.out.println("end convertSSOnametoFullName 0.1 " + FullName + " " + dc.getCount());
    return FullName; 
}

Getting The User ID From IBM Connections

IBM Connections has an evil setting that disables emails from being seen on the system, this is meant as a security setting, but it not only hides the email from the front end but from all the atom feeds, suddenly you don’t have a unique key to reference users on functions.

Well that’s not exactly true, you have a internal ID that IBM functions will accept instead e.g “A7B2C512-27E5-596C-8625-7AD4000843Z3”, the only problem is how in the heck do you GET HOLD OF IT?

If you are within a community you can get it from member lists and such but how about when you want to just want to get a name that is on the system, say to add it to a community, the only way I have found is to search for it and no you cant search for it by email as that is disabled.

You have to search for it by name which while not exactly precise does the job but even that is not an ultra easy function, here is a little function to do it for you.

    private String getUserID( String adminUserName, String adminPassword, String baseURL, String ProfileSearchURL, String SearchUser) throws NotesException {
        String Unid = "";
        try {
            // example values for the passed parameters.
            adminUserName = "administrator"; 
            adminPassword = "password"; 
            baseURL = "https://www.myconnectionsSite.com"; 
            ProfileSearchURL = "/profiles/atom/search.do?name="; //sthis is what connections 4 uses 
            SearchUser = "John Smith";
            
            //build our search string
            String searchString = baseURL + ProfileSearchURL + URLEncoder.encode(SearchUser, "UTF-8");
            
            Abdera abdera = new Abdera();
            AbderaClient client = new AbderaClient(abdera);
            //ensure we can handle SSL requests
            AbderaClient.registerTrustManager();
            //logon to connections
            client.addCredentials(baseURL, null, null, new UsernamePasswordCredentials(adminUserName,adminPassword));
            
            //make the call
            ClientResponse resp = client.get(searchString );
                        
            XPath xpath = abdera.getXPath();
                       
            //get the xml back an navigate to the root of the document
            org.apache.abdera.model.Document doc = resp.getDocument();
            Feed feed = (Feed) doc.getRoot();          
             
            //if there is more than 1 returned entry then the user name is none unique and we cant trust our response
            if (xpath.numericValueOf("count(/a:feed/a:entry)", feed).intValue() > 1) {
                System.out.println("Ack we have more than 1 user with that name I cant be certain I have got the right one");
            } else {
                //as there is only one entry we can navigate straight down to the contributer sectnio which contains the user id
                Object contrubuternode = xpath.selectSingleNode("/a:feed/a:entry/a:contributor", feed );
                //because of the "snx" part we cant get the value without passing xpath our namespaces from the feed so it knows what "snx" means
                String snxUserid = xpath.valueOf("snx:userid", (Base) contrubuternode, feed.getNamespaces());
                
                Unid = snxUserid;   
            }

        } catch (ClassCastException e) {
            System.out.println("no user found or some other garbage return");
            e.printStackTrace();
            Unid = ""; 
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return Unid;
    } 

Migrating to Markdown Pt3 From Domino To Markdown

Domino and in particular BlogSphere V3 by the wonderful Declan Lynch has been my blogging platform for years now and has served me well and faithfully, but all good things come to and end as part of the constant fiddling with new stuff LDC do, I have moved my blogging platform to markdown ( The wretch Ben Poole got me started on it ) on the Statamic platform, but what about years of blog entries that are happily snuggled down in my nsf file,

Java to the rescue. I have done a little agent that takes all blogs and exports them to markdown format regardless if they are html of rich text, glues the existing comments on the end of the blog posts, exports quick images and emoticons (while changing their references in the blog posts) and makes a redirects file so all your old external links work

Just copy the below code into a Java agent and set the ‘baseExportDir’ to where ever you want the site to export too. when you run the agent you will end up with a bunch of mark down files representing the blog entries, a redirects.txt file containing the 301 redirects who’s contents you can past into your root directory .htaccess file so all your old link work, a “page” directory with the emoticons in it, and a “blog” directory with all the quick images in it.

As always, yell if there is something missing or wrong

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.Normalizer;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;
import java.util.regex.Pattern;

import lotus.domino.AgentBase;
import lotus.domino.AgentContext;
import lotus.domino.Database;
import lotus.domino.DateTime;
import lotus.domino.Document;
import lotus.domino.DocumentCollection;
import lotus.domino.EmbeddedObject;
import lotus.domino.NotesException;
import lotus.domino.RichTextItem;
import lotus.domino.Session;
import lotus.domino.View;

public class JavaAgent extends AgentBase {
    public void NotesMain() {
        try {
            Session session = getSession();
            AgentContext agentContext = session.getAgentContext();
            Database db = agentContext.getCurrentDatabase();

            String baseExportDir = "C:\\markdownexport\\";

            // ****** start document export ******

            View content = db.getView("vw_Content_Blogs");

            File theDir = new File(baseExportDir);
            if (!theDir.exists())
                theDir.mkdir();

            // create a file to store all the 301 redirections for existing blog
            // entires

            File redirectfile = new File(baseExportDir + "redirects.txt");

            Writer redirect = new BufferedWriter(new FileWriter(redirectfile));

            Document doc = content.getFirstDocument();
            while (doc != null) {
                Writer output = null;

                if (doc.getItemValueString("FORM").equals("content_BlogEntry")) {

                    String filename = "";

                    Vector dM = doc.getItemValue("EntryDate");
                    DateTime dt = (DateTime) dM.elementAt(0);
                    System.out.println(dt.getLocalTime());
                    Date date = dt.toJavaDate();

                    Calendar cal = Calendar.getInstance();
                    cal.setTime(date);

                    // create file name in the correct format for statamic
                    filename = Integer.toString(cal.get(Calendar.YEAR)) + "-" + String.format("%02d", cal.get(Calendar.MONTH) + 1) + "-" + String.format("%02d", Integer.valueOf(cal.get(Calendar.DAY_OF_MONTH)));
                    filename = filename + "-" + doc.getItemValueString("EntryTitle").trim().replaceAll(" ", "-").replaceAll("\\\\", "-").replaceAll("/", "-").replaceAll(":", "-").replaceAll("\\?", "-").replaceAll("\"", "-");

                    File file = new File(baseExportDir + filename + ".md");
                    redirectfile.getParentFile().mkdirs();

                    // add redirect to redirect file
                    redirect.write("Redirect 301 /d6plinks/" + doc.getItemValueString("PermaLink") + " /blog/" + filename);
                    redirect.write("\r\n");

                    output = new BufferedWriter(new FileWriter(file));
                    output.write("---");
                    output.write("\r\n");
                    output.write("title: '" + doc.getItemValueString("EntryTitle").trim() + "'");
                    output.write("\r\n");
                    if (doc.getItemValueString("EntryStatus").trim().equals("Published")) {
                        output.write("status: live");
                        output.write("\r\n");

                    } else {
                        output.write("status: draft");
                        output.write("\r\n");

                    }

                    // end of meta
                    output.write("---");
                    output.write("\r\n");
                    output.write("\r\n");

                    String body = "";

                    // get the body text and clean it up for UTF-8 standard

                    if (doc.getItemValueString("EntryHTML").trim().length() > 1) {
                        body = doc.getItemValueString("EntryHTML").trim();
                    } else {
                        body = doc.getItemValueString("EntryRICH").trim();
                    }

                    body = Normalizer.normalize(body, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""); // .replaceAll("[^\\p{ASCII}]",
                    // "");

                    // smart single quotes and apostrophe

                    body = removeMSRubbish(body);

                    output.write(body.replaceAll("http.*?\\$File", "/assets/img/blog"));

                    output.write("\r\n");
                    output.write("\r\n");

                    // get all the old comments and add them to the bottom of
                    // the blog

                    DocumentCollection responses = doc.getResponses();

                    if (responses.getCount() > 0) {
                        output.write("Old Comments");
                        output.write("\r\n");
                        output.write("------------");
                        output.write("\r\n");
                        output.write("\r\n");

                        Document rdoc = responses.getFirstDocument();
                        while (rdoc != null) {
                            // setting as h5 in markup
                            output.write("##### " + rdoc.getItemValueString("nameAuthor") + "(" + rdoc.getCreated().toString() + ")");
                            output.write("\r\n");

                            String comment = Normalizer.normalize(rdoc.getItemValueString("body").replaceAll("http.*?\\$File", "/assets/img/page"), Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").replaceAll("[^\\p{ASCII}]", "");
                            comment = removeMSRubbish(comment);

                            output.write(comment);
                            output.write("\r\n");
                            output.write("\r\n");

                            rdoc = responses.getNextDocument(rdoc);
                        }
                    }
                    output.close();
                }
                doc = content.getNextDocument(doc);
            }

            redirect.close();
            // ****** end document export ******

            // ****** start export quick images, the html references
            // '/assets/img/blog' but im just exporting them to 'blog'******
            String imagesExport = "blog\\";

            View view = db.getView("lkp_QuickImages");
            doc = view.getFirstDocument();

            File theImagesDir = new File(baseExportDir + imagesExport);
            if (!theImagesDir.exists())
                theImagesDir.mkdir();

            boolean saveFlag = false;
            while (doc != null) {
                RichTextItem body = (RichTextItem) doc.getFirstItem("ImageFile");
                Vector v = body.getEmbeddedObjects();
                Enumeration e = v.elements();
                while (e.hasMoreElements()) {
                    EmbeddedObject eo = (EmbeddedObject) e.nextElement();
                    if (eo.getType() == EmbeddedObject.EMBED_ATTACHMENT) {
                        eo.extractFile(baseExportDir + imagesExport + eo.getSource());
                    }
                }

                doc = view.getNextDocument(doc);
            }
            // ****** end export quick images ******

            // ****** start export emoticon images, the html references
            // '/assets/img/page' but im just exporting them to 'page'******
            imagesExport = "page\\";

            view = db.getView("lkp_Emoticons_Web");
            doc = view.getFirstDocument();

            theImagesDir = new File(baseExportDir + imagesExport);
            if (!theImagesDir.exists())
                theImagesDir.mkdir();

            while (doc != null) {
                System.out.println(doc.getItemValueString("EmoticonName"));
                Vector v = session.evaluate("@AttachmentNames", doc);
                System.out.println("emoticon:" + v.firstElement().toString());
                EmbeddedObject eo = doc.getAttachment(v.firstElement().toString());
                eo.extractFile(baseExportDir + imagesExport + v.firstElement().toString());
                doc = view.getNextDocument(doc);
            }
            // ****** end export images ******

        } catch (NotesException e) {
            System.out.println(e.id + " " + e.text);
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public String removeMSRubbish(String body) {
        body = body.replaceAll("[\u2018|\u2019|\u201A]", "\'");
        // smart double quotes
        body = body.replaceAll("[\u201C|\u201D|\u201E]", "\"");
        // ellipsis
        body = body.replaceAll("\u2026", "...");
        // dashes
        body = body.replaceAll("[\u2013|\u2014]", "-");
        // circumflex
        body = body.replaceAll("\u02C6", "^");
        // open angle bracket
        body = body.replaceAll("\u2039", "<");
        // close angle bracket
        body = body.replaceAll("\u203A", ">");
        // spaces
        body = body.replaceAll("[\u02DC|\u00A0]", " ");

        return body;
    }

}

Eclipse Eclipse everywhere and nere a bit of common sense

Now look you software makers, its all very well saying “built on eclipse”, but i now have 3 completely separate instances of eclipse (Notes 8.5, Myeclipse and Flex3) on my machine and none of them seem to be talking to each other (I’ve even just opened them up all at once to see if there was a glimmer of recognition between them, I KNOW you can check for such thinks during an install because vmware workstation DOES for its debugger, I’m not asking for perfect integration, but come on party people, standard platform is nice, but COMMON standard platform would be better, all you have to do is ask “do you have an existing eclipse workspace you wish to install to”. (mutters and trundles off to see if he can glue them together because most of the players don’t seem to like you just switching to a common workspace, there must be an article to be written in getting these to all work)….end of rant

Latest Blogs