How to Merge PDF Files with PDFBox in Java?

Merging PDF files in java is made easier with Apache PDFBox.

The codes below illustrate how to sort and merge all PDF files found in a particular directory according by their last modified date:

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;

import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.util.PDFMergerUtility;

public class test {
 public static void main(String[] args) throws IOException, COSVisitorException {
  int maxPdf = 1000; // use this to troubleshoot java/lang/OutOfMemoryError exception
  String pdfDirPath = "C:\\circulars_and_notices\\pdfs\\port_marine_notices";

  File pdfDir = new File(pdfDirPath);
  if (pdfDir.isDirectory()) {
   // proceed to crawl thru the folder and merge the pdf according to last mod date
   File[] pdfs = pdfDir.listFiles();
   int cnt = pdfs.length;

   if (cnt > 0) {
    // sort the pdfs by last mod date in desc order
    Arrays.sort(pdfs, new Comparator() {
    public int compare(File f1, File f2) {
     return Long.compare(f2.lastModified(), f1.lastModified());
     }
    });

    if (maxPdf != 0 && maxPdf < cnt) {
     cnt = maxPdf;
    }

    // start add merging sources
    PDFMergerUtility pdfMerger = new PDFMergerUtility();

    // set destination
    pdfMerger.setDestinationFileName("C:\\mergeDocs.pdf");

    // add in pdf source
    for (int i = 0; i < cnt; i++) {
     File file = pdfs[i];
     pdfMerger.addSource(file);
    }

    // merge pdfs
    pdfMerger.mergeDocuments();

   } else {
    System.out.println("Target directory is empty.");
   }
  } else {
   System.out.println("Target is not a directory (" + pdfDirPath + ").");
  }
 }
}

The codes above should works fine in most scenarios. But if you are merging large PDFs files like in my case, then the chances of encountering “java/lang/OutOfMemoryError” exception is high. Of course a quick solution is to increase heap size but this will only be a temporary solution.

Lucky for us, PDFBox offers another alternative way of merging PDFs by storing the PDF streams into a temp file. See below code for illustrations:

// codes

[21/4/2014] We have encountered org.apache.pdfbox.exceptions.COSVisitorException: java.lang.NullPointerException when we tried to merge large number of PDFs (<850 pdfs) at once. Hence we decided to revise our codes by merge our PDFs in smaller quantities before merging them as one.

How to Access SharePoint Web Services via Javascript?

Been tasked by my boss with another tight deadline project (understand client’s requirement and developed 2 mobile app within 1 month). Both of the apps required to read data from SharePoint Web Services using Javascript.

Before we proceed, it is important to know what are the types of SharePoint web services that are available to us? Click here for a list of SharePoint services.

Next, we need to understand how to read and call web services’ methods. In the example below, we will demonstrate how we derived and constructed our SOAP message to retrieve list’s items (API: List.GetListItems method):

  1. Always wrapped our SOAP message block with the following header and footer:
    Header

    <soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'>
     <soapenv:Header/>
     <soapenv:Body xmlns='http://schemas.microsoft.com/sharepoint/soap/'>
    

    Footer

     </soapenv:Body>
    </soapenv:Envelope>
    
  2. Construct the web service’s method and parameters exactly as shown in MSDN. In our example, we will input listName and sort the result according to the title:
    <GetListItems>
     <listName>{A4E4AA50-CB01-48C4-9D24-DD6404CD70C1}</listName>
     <query>
      <Query>
       <OrderBy>
        <FieldRef Ascending='TRUE' Name='Title'/>
       </OrderBy>
      </Query>
     </query>
    </GetListItems>
    

Lastly wrapped the SOAP message that you form above and sent it thru using jQuery.ajax method (example like the one below):

$.ajax({
 type: "POST",
 url: url /* the url will be as stated in MSDN Web Reference, for all list related services, it will be http://<Site>/_vti_bin/Lists.asmx*/,
 data: q.join("") /* the soap message that we have construct earlier*/,
 contentType: "text/xml; charset=utf-8",
 dataType: "html" /* can be xml */,
 success: function(xdata){
  result.css("color","green").html($('<div/>').text(xdata).html());
 },
 error: function(a,b,c){
  result.css("color","red").html("Unable to connect ("+c+").");
 }
});

WebSphere Portal: AuthorizationException Is Thrown While Calling Workspace getById Method

Below looks like an innocent block of codes written to retrieve WCM items:

 Content content = null;
 Workspace ws=WCM_API.getRepository().getWorkspace();
 DocumentIdIterator iter = ws.findByPath(path, Workspace.WORKFLOWSTATUS_ALL);

 if(iter.hasNext()) {
  DocumentId docId = iter.nextId();
  if (docId.isOfType(DocumentTypes.Content)) {
   content = (Content)ws.getById(docId,true,true);
  }
 }

But as soon as you try to retrieve WCM items with “User” access, an “AuthorizationException” will be thrown (even though you are able to view the items in WCM Authoring Portlet). This is because getById method by default required min “Contributor” rights.

In order to resolve the issue, call Workspace.useUserAccess(true) before you call the getById method.

WebSphere Portal: Customize Authoring Template’s Elements With JSP

WebSphere Portal allows you to customize some of the Authoring Template’s elements using “Custom JSP” field (usually we would use TEXT element type for out of the box customization). This feature will comes in handy when our clients have special requirements which Authoring Template cant fulfil. Naming a few examples that we have encountered:

  • “Can you display the email’s WCM url in the Authoring Template (refers to the email that is send by WCM Workflow Email Action)?”
  • “Can we have a Color picker function like the JSColor (http://jscolor.com)?”
  • “Can we have a dynamic product list where we can sort using drag and drop action, as we already have this feature in our old system?”
  • and so on…

Anyway, there are currently 2 ways to reference custom jsp:

  • Stored the jsp within the respective application folders (like PA_WCM_Authoring_UI and PA_WCMLRingPortJSR286) and reference the custom jsp by /jsp/html/xxx.jsp.
  • *Our Preferred Method* Or stored the jsp in one of the application folders and reference it using the following path: contextPath;<jsp path>.
    For example, we would place our custom jsp in PA_WCM_Authoring_UI folder (<wp_profile>\installedApps\<cell>\PA_WCM_Authoring_UI.ear\ilwwcm-authoring.war\jsp\html\custom) and we would enter “/wps/PA_WCM_Authoring_UI;/jsp/html/custom/xxx.jsp” as our custom jsp path.

In order to fully replicate existing Authoring Template’s behaviour (Read, Edit and Render modes), usually we would need to create at least 2 custom jsp (for Read and Edit modes) and save them into the PA_WCM_Authoring_UI folder. In some rare cases, we usually do not need Render mode to String.split the result in Presentation Temple.

Below is a simple example of a color picker example (I have omit out the colour selection feature as it makes the post unwanted draggy):

Step 1: Create AT-ColourSelection_EditMode.jsp for Edit Mode

<%@ page import="com.ibm.workplace.wcm.app.ui.portlet.widget.CustomItemBean"%>
<%
    // Retrieve the custom item bean
    CustomItemBean customItem = (CustomItemBean) request.getAttribute("CustomItemBean");
    
    // Initialize variable
   String fieldName = customItem.getFieldName();
   String value = (String)customItem.getFieldValue();
		
    // Set the name of the submit function
    customItem.setSubmitFunctionName(fieldName + "_mysubmit");
%>

Color : <input id="<%=fieldName%>_txtColor" type="text" value="<%=value%>"></input>

<script language='Javascript'>
<!-- Submit function for multifield, collapses fields into one -->
function <%=fieldName%>_mysubmit()
{
document.getElementById("<%=fieldName%>").value = document.getElementById('<%=fieldName%>_txtColor').value;
}
</script>

Step 2: Create AT-ColorSelection_ReadMode.jsp for Read Mode.
The custom jsp should just contains codes that are sufficient for reading purpose.

<%@ page import="com.ibm.workplace.wcm.app.ui.portlet.widget.CustomItemBean"%>
<%
    // Retrieve the custom item bean
    CustomItemBean customItem = (CustomItemBean) request.getAttribute("CustomItemBean");
    
    // Initialize variable
   String fieldName = customItem.getFieldName();
   String value = (String)customItem.getFieldValue();
%>

Color : <input id="<%=fieldName%>_txtColor" type="text" value="<%=value%>"></input>

Step 3: Add in a Text element in Authoring Template and set the Custom JSP path

readMode=wps/PA_WCM_Authoring_UI;/jsp/html/customJSP/AT-ColourSelection_ReadMode.jsp,editMode=wps/PA_WCM_Authoring_UI;/jsp/html/customJSP/AT-ColourSelection_EditMode.jsp

 

More Examples:

WebSphere Portal: Setting up SMTP for WCM Workflow

Do the following to configure your SMTP server for WCM workflow notification:

  1. Log in to WebSphere Integrated Solutions Console (https://<server ip>:<port:10032>/ibm/console).
  2. Go to “Resource Environment > Resource Environment Providers” and click on “WCM WCMConfigService.
  3. Click on “Custom properties” link.
  4. Add in the following properties:
    1. connect.connector.mailconnector.defaultsmtpserver = Smtp server ip / hostname (mail.yourmailserver.com)
    2. connect.connector.mailconnector.defaultfromaddress = Default From email address (auto@yourmailserver.com)
    3. connect.connector.mailconnector.defaultreplytoaddress = Default Reply-to Field (auto@yourmailserver.com) *optional*
  5. If you are using a secure SMTP server, add in the following properties as well:
    1. connect.connector.mailconnector.defaultusername = Username
    2. connect.connector.mailconnector.defaultpassword = Password
  6. If your SMTP server is running other than port 25, add in “connect.connector.mailconnector.defaultsmtpport” and state the port.
  7. Restart portal.

WebSphere Portal: How to get HttpServletRequest and HttpServletResponse from Portlet Request?

Like most portlet developments, there are some scenarios where you might need to retrieve HttpServletRequest and HttpServletResponse from Portlet Request (RenderRequest/RenderResponse/ActionRequest/ActionResponse etc).

In WebSphere Portal, there is a PortletUtils utility class that provides the method you need:

import com.ibm.ws.portletcontainer.portlet.PortletUtils;

// call the respective methods to get HttpServletRequest/HttpServletResponse you need
PortletUtils.getHttpServletRequest(request);

How to Create WebSphere Portal Theme

<Incomplete at the moment> This article merely follows the steps listed in Theme Customization 8.0 Quick Reference with bits and pieces of my notes.

Step 1: Copy The Static Resources for Your Theme:

  1. You will need a WebDAV client for this exercise. I am currently using Cyberduck WebDAV client (always had a thing with rubber duck).
  2. Connect to http://<server ip>:<port>/wps/mycontenthandler/dav/themelist with WebDAV client.
    Note: in our screenshot, you might notice that we are pointing to “/web/” instead of the default “/wps/“, please ignore that as we are trying out other configuration at the same time. By default, it will be pointing to “/wps/”.
  3. Download ibm.portal.80Theme to your local disk and rename the folder to your custom theme’s name (like corporateTheme).
  4. Locate localized_en.properties in the metadata folder. Change the “title” value  to your custom theme’s display title.
    (if you intended to support locale other than en, please edit respective locale files accordingly)
  5. Edit the metadata.properties file and replace Portal8.0 in “com.ibm.portal.layout.template.href value to your new custom theme’s name (As set in point 3).
    com.ibm.portal.layout.template.href=dav\:fs-type1/themes/<custom theme name>/layout-templates/2ColumnEqual/
  6. Upload your new custom theme back to http://<server ip>:<port>/wps/mycontenthandler/dav/themelist.
  7. Go to Administration page (Administration > Portal User Interface > Themes and Skins) to verify that your new custom theme has been successfully deployed. The theme’s display name should looks exactly as set in the localized_en.properties file.
What we have done so far?
We have successfully replicate the theme's static resources (as in the "frontend"), but the new theme is still referencing the default theme's dynamic resources (css/js/navigation, page menu, footer etc). 

To understand the static resources portion, go to http://<server ip>:<port:10039>/wps/mycontenthandler/dav/fs-type1/themes/<custom theme>/nls and play around with theme_en.html file. (depending on client's requirements, sometimes the theme customization can be stop at this stage)

Step 2: Modify The Dynamic Resource References For Your Theme:

  1. Log in to WebSphere Integrated Solutions Console (https://<server ip>:10032/ibm/console/logon.jsp) and go to “Resource Environment > Resource Environment Providers“.
  2. Click on “WP DynamicContentSpotMappings“, it should be at page 2 by default.
  3. Click on “Custom properties“.
  4. <//to be continue>
  5. Restart the server if your changes is not reflecting.

References:

  1. Theme Customization 8.0 Quick Reference

WebSphere Portal: Theme Development Useful References

WebSphere Portal Library Tags | link
Our trusty library tags since WebSphere Portal 6.0 (maybe earlier)!
(The tags mentioned are only for use in theme and skin JSPs. Do not use portal tags in portlet JSPs.)

WebSphere Portal EL Beans | link
Since WebSphere Portal 7.0, Expression Language (EL) beans has been added for easy access to WebSphere Programming models. The beans are accessed in the global wp namespace. For more information on specific models, refer to the Portal 8.0 SPI Javadoc external link. Examples of the common used beans:

  • ${wp.metadata[node]}
  • ${wp.node.contentNode}
  • ${wp.identification[node]}
  • ${wp.node.metadata['metaDataKey']}
  • ${wp.navigationModel}

Missing from the documentation:

  • “INTERNALURL” is missing from ContentNodeBean.contentNodeType documentation.

WebSphere Portal EL Available Variables (WebSphere Portal 8.0)
Do remember to check out bootstrap.jspf before init your variables, as some of the useful variables have already been initialized by the jsp fragment. Examples:

  • ${pageTitle} – current page’s title
  • ${currentNavNode} – current page’s navigation node
  •  ${aggMD} – an aggregated metadata of the current page
  • ${deviceClass} – device class, values available [<empty string>/tablet/smartphone]

Setting Dynamic Content Spot Mapping with mvc:URI | link
The mvc:URI scheme is a special URI format that accesses different resources, depending on the device class. This scheme is used by the Portal 8001 theme in the definition of several dynamic content spots. Example of possible combinations:

  • mvc:res:/hello.jsp: Uses a single default URI.
  • mvc:res:/hello.jsp,smartphone@res:/hello_smartphone.jsp: Uses res:/hello.jsp as the default URI and res:/hello_smartphone.jsp as the URI for smartphones.
  • mvc:res:/hello.jsp,smartphone/tablet@res:/hello_mobile.jsp: Uses res:/hello.jsp as the default URI and res:/hello_mobile.jsp as the URI for smartphones and tablets.
  • mvc:res:/hello.jsp,smartphone@,tablet@res:/hello_tablet.jsp: Uses res:/hello.jsp as the default URI and res:/hello_tablet.jsp as the URI for tablets. No URI is assigned for smartphones.

2014 – Big Leap Forward

2014-new-chapter

Being forced to wear the bunny’s ear T.T

Other than hitting the big 30, it is also the year which we will be receiving our flat and start preparing for our marriage in 2015. Another step closer in starting out a family on our own! A rough schedule for us to follow:

  • Jan – Apr: We will be busy brainstorming for renovation ideas and things to look out for (especially hidden renovation costs).
    Our research sources: Interior Design Magazine, Blogs and Friends (House-warming)!
  • May – Jun: Source for quotations based on our design and decided if we should go for ID or manage the contractors on our own.
  • Jul – Sep: Should be collecting our keys during this period, perhaps we can use this time to plan for our wedding! Again our main wedding research sources will be: Wedding Magazine, Blogs and Friends!
  • Nov – Dec: Renovation completed! *random estimation*

Side Note for Me:
While we will be busy with our renovation and marriage’s preparation, we should not neglect spending time with our parents. Being 30 would also means that our parents is in their last stage of life. As one aged, we (everyone) tended to get stubborn and forgetful. Talking to us suddenly becomes a frustrating affair.

So before we get angry with your parents, take a deep breath and think.

Have your parents ever neglect you when you are still a baby (while you wake them in the middle of the night for milk)? How many times your parents have been putting up with your stubbornness while they are just trying to teach you the right values?

Or how many times they have been there for you when you need help? Now this is the time where they need you the most, don’t let them down. Lastly think of the quote below and reflect upon yourself whenever you get frustrated:

Love your parents and treat them with love and care. For you will only know their value when you see their empty chair.

Empty Rocking Chair on a Deck

WebSphere Portal 8: Remove State Information From Friendly URL

By default, WebSphere Portal 8 will include navigational state information. While state information comes in handy by retaining the page’s state, it is often redeemed by our clients as messy and gibberish especially for public facing websites. An example of url with state information:

http://10.10.10.18:10039/wps/portal/test/!ut/p/a1/hc5BDoIwEAXQs3iCfptSyhIpUIyKRBO1G8OCYBOkBtDzWwwbF-LsJnl__hBNzkS35cvU5WBsWzakiLvOdoeq791-NPfKPocRaX7N9zJdUkHXqUqAMM89hsIHJHXg4gB-TIhPPkpDxfwNIKSMkMlVwHfcA2JM-Rnwp_9E9HcFmKDjBSX9YAtkfAIzLz5uTRJk9eINqfbQ7A!!/dl5/d5/L2dBISEvZ0FBIS9nQSEh/

Before we proceed, try this! Access your page’s url without state information. You will realize that you will be redirected to an url with state information.

Step 1: Add/Edit “friendly.redirect.enabled” Property in WebSphere Integrated Solutions Console:

  1. Login to WebSphere Integrated Solutions Console (https://<server ip>:<port:10032>/ibm/console).
  2. Click on “Resource Environment Provider“, under “Resources > Resource Environment > Resource Environment Providers“.
  3. Search for “WPConfigService” and click on it.
  4. Click on the “Custom Properties“.
  5. Search for “friendly.redirect.enabled” property. By default the property does not exists, add in “friendly.redirect.enabled” and set it to false (type as string).
  6. Save the setting to master configuration.
  7. Restart the server.

Step 2: Update Theme Parameter via XML Access

  1. Go to <PortalServer folder>\bin folder (for example: C:\IBM\WebSphere\PortalServer\bin).
  2. Create configureTheme.xml in <PortalServer folder>\bin folder. Add in the following content in the xml.
    <?xml version="1.0" encoding="UTF-8"?>
    
    <request xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="PortalConfig_8.0.0.xsd" type="update">
       <!-- This sample sets the hasBaseURL Tag in the Portal 8 Theme. -->
       <portal action="locate">
          <theme action="update" uniquename="ibm.portal.80Theme" >
             <parameter name="com.ibm.portal.theme.hasBaseURL" 
                        type="string" update="set">true</parameter>
          </theme>
       </portal>
    </request>
  3. Open your cmd prompt and go to <PortalServer folder>\bin
  4. Key in the following cmd and press enter.
    xmlaccess.bat -in configureTheme.xml -out result.xml -user <wpsadmin's name> -password <wpsadmin's password> -url http://<server ip>:<port:10039>/web/config
  5. You should be able to see the following response in your cmd prompt:
  6. To verify access one of the page’s url that is using the default theme without the state information (for example: http://10.10.10.18:10039/wps/portal/test). You will realize that you are no longer been redirect to a url with state information.
    Note: if you click on any of the links in the theme, you will still be directed to an url with state information and this brings us to the next step.

Step 3: Update Theme Dynamic Content Spots

  1. Go to <PortalServer folder>\theme\wp.theme.themes\default80\installedApps\DefaultTheme80.ear\DefaultTheme80.war\themes\html\dynamicSpots.
  2. Open navigation.jsp with an editor (would prefer Notepad++).
  3. Search for the string “<a href=”?uri=nm:oid:${nodeID}” and replace it with the following string:
    <a href="<portal-navigation:urlGeneration contentNode="${nodeID}" keepNavigationalState="false"><%wpsURL.write(out);%></portal-navigation:urlGeneration>"
  4. Repeat Step 2 and 3 with sideNavigation.jsp (in the same folder as navigation.jsp).

Step 4: Ensure that all of your pages have friendly name

  1. Scan thru all of your pages and ensure that they have friendly URL name.
  2. Congrats! You are done with this excerise!

References: