Generate PDF from server side using Java

You can use OpenPDF to draw a PDF (the open source fork of iText). However, the learning curve of creating PDF with layouts and stuff is very high.

A more cost-effective solution is create an HTML version of it and convert it using Flying Saucer with its OpenPDF component

. As a Java developer you are already familiar with html + css, so doing that is quick.

There are some limitations and side effects of this approach

  • Your html must be XML/XHTML
  • Styles’ version must be no more than CSS 2.1. (So you can’t use flexbox layout)
  • No javascript
  • More: https://flyingsaucerproject.github.io/flyingsaucer/r8/guide/users-guide-R8.html#xil_6
  • The image quality seems worse than what you see on the html page, if there is any

And to create a PDF, your html can’t be too wide, otherwise content may be lost.

Typically, say you are creating an A4 PDF with margin of 0.1 inch on both left and right, the width of your html should not be more than (A4’s paper width – margin) * pixe density = (8.27inch – 0.1 inch * 2) * 96 DPI = 774 pixels

And here is how to set up the size and margin of the PDF in your html:

<head>
    <style>
        @page {
            margin-left: 9px;   /* about 0.1 inch */ 
            margin-right: 9px; 
            size: A4;
        }
    </style>
   ...

Redux + Typescript: The type of the root state

You can get the type of the state from the root reducer

// the reducer
const appReducer = combineReducers({
    global: globalStateReducer,
    example: exampleReducer
});

type AppState = ReturnType<typeof appReducer>

However the redux state can be cleared in some scenarios, so it should be undefinable

//See https://stackoverflow.com/a/35641992/301447 for "undefined redux state" 
type RootState = AppState | undefined;


const rootReducer = (state:RootState, action: Action) => {
    if (action.type === DESTROY_REDUX_STATE) {
        state = undefined;
    }
    return appReducer(state, action)
}

And mapStateToProps() of a component will look like this:

function mapStateToProps(rootState: RootState) {
    return {
        example: rootState!.example  //note the exclamation mark as rootState can be undefined 
    };
}

Redux + Typescript: Use class to define Actions

You want the actions to be typed. You should use interface or class.

Class is better than interface because in interfaces you cannot define the action "type" as a constant, but in classes you can:

class LoadingStartedAction implements Action{
    readonly type = LOADING_STARTED
}

However a class instance is not a plain object, but Redux requires plain objects to be dispatched.

This can be handled by a middleware which converts the class instances to plain objects

// See https://github.com/reduxjs/redux/issues/992#issuecomment-167964652
const typedActionToPlain = (store: any) => (next: any) => (action: any) =>  {
    next(Object.assign({}, action));
};

export default typedActionToPlain;

Pay attention not to step into the toe of Thunk middleware when registering it:

const rootStore = createStore(rootReducer,
    applyMiddleware(thunkMiddleware,
        typedActionToPlain //this must be after thunk, otherwise it will try to convert an async function to a plain object
    )
);

Read process output in Java without hanging the main thread

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;

/**
 * 
 * @author chenjianjx (Copied from CXF)
 *
 */
public class StreamPrinter extends Thread {
  InputStream is;
  String msg;
  OutputStream os;

  public StreamPrinter(InputStream stream, String type) {
    this(stream, type, null);
  }

  public StreamPrinter(InputStream stream, String type, OutputStream redirect) {
    is = stream;
    msg = type;
    os = redirect;
  }

  public void run() {
    try {
      PrintWriter pw = null;
      if (os != null) {
        pw = new PrintWriter(os);
      }
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      String line = br.readLine();
      while (line != null) {
        if (pw != null) {
          pw.println(msg + " " + line);
        }
        line = br.readLine();
      }
      if (pw != null) {
        pw.flush();
      }
    } catch (IOException ioe) {
      throw new RuntimeException(ioe);
    }
  }
}
/**
 * 
 * @author chenjianjx
 * 
 */

public class SystemProcessException extends Exception {

  private int returnCode;
  private String consoleOutput;

  /**
   * 
   */
  private static final long serialVersionUID = -6904952219344675845L;

  public SystemProcessException(int returnCode, String consoleOutput) {
    super();
    this.returnCode = returnCode;
    this.consoleOutput = consoleOutput;
  }

  public int getReturnCode() {
    return returnCode;
  }

  public String getConsoleOutput() {
    return consoleOutput;
  }

}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.jaxws.util.io.StreamPrinter;

/**
 * Spawning a process from the OS
 * 
 * @author chenjianjx
 * 
 */
public class SystemProcessUtils {

  /**
   * execture a program and returns console output
   */
  public static String exec(String[] cmdArray) throws SystemProcessException {

    try {
      Process p = Runtime.getRuntime().exec(cmdArray);
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      Thread errStreamThread = collectErrorStream(p, output);
      Thread inputStreamThread = collectInputStream(p, output);
      int returningCode = p.waitFor();
      int timeout = 60000;
      if (errStreamThread != null) {
        errStreamThread.join(timeout);
      }
      if (inputStreamThread != null) {
        inputStreamThread.join(timeout);
      }
      String consoleOutput = new String(output.toByteArray());
      System.out.println(consoleOutput);
      if (returningCode != 0) {
        throw new SystemProcessException(returningCode, consoleOutput);
      }
      return consoleOutput;
    } catch (IOException e) {
      throw new IllegalStateException(e);
    } catch (InterruptedException e) {
      throw new IllegalStateException(e);
    }
  }

  private static StreamPrinter collectInputStream(Process p, OutputStream out) {
    if (p.getInputStream() != null) {
      StreamPrinter infoStreamPrinter = new StreamPrinter(p.getInputStream(), "[INFO]", out);
      infoStreamPrinter.start();
      return infoStreamPrinter;
    }
    return null;
  }

  private static StreamPrinter collectErrorStream(Process p, OutputStream out) {
    if (p.getErrorStream() != null) {
      StreamPrinter errorStreamPrinter = new StreamPrinter(p.getErrorStream(), "[ERROR]", out);
      errorStreamPrinter.start();
      return errorStreamPrinter;
    }
    return null;
  }

}

Gradle upload a custom-generated jar file to Nexus

Do this:

apply plugin: 'maven-publish'


   publishing {
        publications {
            maven( MavenPublication ) {
                artifact theTaskThatGeneratesJar
            }
        }
        repositories {
            maven {
                def releasesRepoUrl = http://.../repositories/releases/'
                def snapshotsRepoUrl = 'http://.../repositories/snapshots/'
                url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
                credentials {
                    username 'xxx'
                    password 'xxx'
                }
            }
        }

    }


Sending html emails with images inside

If the images are external image links that can be accessed by http url, then nothing speical needs to be done.

If the images are external image files, then you have 2 choices.

Embed the images as base64 strings. . This is simple. However email clients such as gmail will refuse to render them

Using cid. This is the most robust way. An example can be found here . In addition, you should set the content type of the image body part as "image/png", otherwise the email receipients will see there are attachments

     
              MimeBodyPart imagePart = new MimeBodyPart();
                DataSource fds = new FileDataSource(imageFile);
                imagePart.setDataHandler(new DataHandler(fds));
                imagePart.setHeader("Content-ID", "<" + imageFile.getName() + ">");
                imagePart.setHeader("Content-Type", "image/png");

React Component: A pair of componentDidMount() and componentWillUnmount() may cause infinite loop if error is thrown from render

The following will cause an infinite loop. The render() method will be called again and again.

class ToDoItemEditComponentContainer extends React.Component {
    componentDidMount() {        
        this.props.loadItem();  //load item remotely and put it in the local redux store      
    }

    componentWillUnmount() {
        this.props.clearItemLocally(); //remove item from local redux store
    }

    render() {
        throw "some error" 
    }
}

Here is why: "As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree. " (See here). To be more specific,

  • render() throws an error
  • The react component tree is unmounted
  • componentWillUnmount() is called. And something is cleared from the reduxt store or react state
  • The component tree will be rendered again since some state has been changed

To fix: Implement componentDidCatch() hook in this component or create a global error boundary as shown in here . When there is a render() error, do not call the render() method any more, or do not change any local data in componentWillUnmount()