Hot questions for Using JasperReports in fonts

Question:

I'm running a process on a test environment that takes more than 10 hours to run and generates PDF documents using Jasper Reports v3.7.5.

Quite frequently the process finishes successfully but in some cases the process fails throwing this exception:

20/05/2017 02:45:23.503 ERROR [process-pool-2-thread-20]  net.sf.jasperreports.extensions.DefaultExtensionsRegistry - Error instantiating extensions registry for simple.font.families
net.sf.jasperreports.engine.JRRuntimeException: java.io.IOException: Problem reading font data.
    at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:77)
    at net.sf.jasperreports.engine.fonts.SimpleFontFamily.createFontFace(SimpleFontFamily.java:316)
    at net.sf.jasperreports.engine.fonts.SimpleFontFamily.setNormal(SimpleFontFamily.java:85)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.parseFontFamily(SimpleFontExtensionHelper.java:233)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.parseFontFamilies(SimpleFontExtensionHelper.java:204)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.loadFontFamilies(SimpleFontExtensionHelper.java:173)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.loadFontFamilies(SimpleFontExtensionHelper.java:142)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory.createRegistry(SimpleFontExtensionsRegistryFactory.java:63)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.instantiateRegistry(DefaultExtensionsRegistry.java:238)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.loadRegistries(DefaultExtensionsRegistry.java:213)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.loadRegistries(DefaultExtensionsRegistry.java:162)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.getRegistries(DefaultExtensionsRegistry.java:132)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.getExtensions(DefaultExtensionsRegistry.java:104)
    at net.sf.jasperreports.engine.util.JRStyledTextParser.<clinit>(JRStyledTextParser.java:76)
    at net.sf.jasperreports.engine.fill.JRBaseFiller.<init>(JRBaseFiller.java:182)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:77)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:87)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:57)
    at net.sf.jasperreports.engine.fill.JRFiller.createFiller(JRFiller.java:142)
    at net.sf.jasperreports.engine.fill.JRFiller.fillReport(JRFiller.java:78)
    at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:624)
    at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:605)
    ...
Caused by: java.io.IOException: Problem reading font data.
    at java.awt.Font.createFont0(Font.java:1000)
    at java.awt.Font.createFont(Font.java:877)
    at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:69)
    ... 120 common frames omitted       

Soon after, on the same thread, the following error is logged. I'm not sure if it is related, though:

20/05/2017 02:45:23.605 ERROR [process-pool-2-thread-20]  my.package.MyClass.NoClassDefFoundError - AbstractReportCreationService.createAndPersistReport(...) threw an error: Could not initialize class sun.awt.X11GraphicsEnvironment

I have tried running this process on Solaris 5.10 and 5.11, using the same Java 1.8.0 version. It occurs randomly on both. I have been trying to reproduce the error in order to find the underlying cause, but to no avail so far.

I've read similar issues reported in StackOverflow and Jaspersoft Community forums. Most of these posts mention possibly permission issues preventing the process from reading from or writing to the java temp directory (java.io.tmpdir = /var/tmp/). See for example:

The method JasperFillManager.fillReport () throws java.io.Exception

I've checked file permissions and these are correct. When the process runs successfully it creates temporary font files called something like +~JF9070759829719582131.tmp or similar random names. Even when the process has failed it has left some of these temporary font files in the /var/tmp/ directory, so permissions doesn't seem to be the issue. On top of that, permissions are not changed between successful and failed runs.

On this post to Jaspersoft Community forum:

http://community.jaspersoft.com/questions/543492/javaioioexception-problem-reading-font-data

a proposed solution is to start Tomcat with option

-Djava.awt.headless=true

The process I'm running uses plain Java, not Tomcat, but I'm willing to try the headless mode. Since the problem occurs randomly and takes so long to run, it is going to be hard to prove that this possible solution actually resolves the issue. I'm worried about deploying this to the production environment and getting the same issue there randomly. Can anyone explain why running in headless mode might fix the issue, or what other possible solution should I try, please?


Answer:

We’ve resolved this exact same issue in our code.

It would occur randomly. We resolved that the actual issue was being swallowed by java and disguised under the Exception

Caused by: java.io.IOException: Problem reading font data.
                at java.awt.Font.createFont0(Font.java:1000)
                at java.awt.Font.createFont(Font.java:877)
                at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:69)
                ... 120 common frames omitted 

So I wrote a piece of code that would replicate all the functionality required to create a font but wouldn’t swallow the true exception. We ran the process in screen (server sided session we can attach & detach from) for 24 hours without any issues.

It wasn’t until we had the user that was initially launching the problem function, execute the new test function in a screen. The following error was produced -

java.io.IOException: Problem reading font data.
        at judson.Main.createFont0(Main.java:203)
        at judson.Main.createFont(Main.java:94)
        at judson.Main.createFont(Main.java:49)
        at judson.Main.main(Main.java:39)
Caused by: java.awt.AWTError: Can't connect to X11 window server using 'localhost:12.0' as the value of the DISPLAY variable.
        at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)

It turns out that the user was using a putty manager, which was automagically setting the DISPLAY variable.

/home/user/judsona/tmp/FontErrorDetector/bin : echo $DISPLAY
localhost:15.0

Further investigation into why it only failed occasionally lead to us to discover that when the session was available (X11 was available) it would work, but as soon as the session disconnected it would fail. Note: this would happen even if the initiating user was detaching from the screen session, it would wait until they closed their session after detaching before failing

So I investigated GraphicsEnvironment code & X11GraphicsEnvironment

X11GraphicsEnvironment.java

static {
    java.security.AccessController.doPrivileged(
                      new java.security.PrivilegedAction() {
        public Object run() {
            System.loadLibrary("awt");

            /*
             * Note: The MToolkit object depends on the static initializer
             * of X11GraphicsEnvironment to initialize the connection to
             * the X11 server.
             */
            if (!isHeadless()) {
                // first check the OGL system property
                boolean glxRequested = false;
                String prop = System.getProperty("sun.java2d.opengl");
                if (prop != null) {
                    if (prop.equals("true") || prop.equals("t")) {
                        glxRequested = true;
                    } else if (prop.equals("True") || prop.equals("T")) {
                        glxRequested = true;
                        glxVerbose = true;
                    }
                }

                // Now check for XRender system property
                boolean xRenderRequested = true;
                boolean xRenderIgnoreLinuxVersion = false;
                String xProp = System.getProperty("sun.java2d.xrender");
                    if (xProp != null) {
                    if (xProp.equals("false") || xProp.equals("f")) {
                        xRenderRequested = false;
                    } else if (xProp.equals("True") || xProp.equals("T")) {
                        xRenderRequested = true;
                        xRenderVerbose = true;
                    }

                    if(xProp.equalsIgnoreCase("t") || xProp.equalsIgnoreCase("true")) {
                        xRenderIgnoreLinuxVersion = true;
                    }
                }

                // initialize the X11 display connection
                initDisplay(glxRequested); 

^ it would initDisplay when it has determined it isn’t headless.

GraphicsEnvironment.java

 /**
 * @return the value of the property "java.awt.headless"
 * @since 1.4
 */
private static boolean getHeadlessProperty() {
    if (headless == null) {
        java.security.AccessController.doPrivileged(
        new java.security.PrivilegedAction<Object>() {
            public Object run() {
                String nm = System.getProperty("java.awt.headless");

                if (nm == null) {
                    /* No need to ask for DISPLAY when run in a browser */
                    if (System.getProperty("javaplugin.version") != null) {
                        headless = defaultHeadless = Boolean.FALSE;
                    } else {
                        String osName = System.getProperty("os.name");
                        if (osName.contains("OS X") && "sun.awt.HToolkit".equals(
                                System.getProperty("awt.toolkit")))
                        {
                            headless = defaultHeadless = Boolean.TRUE;
                        } else {
                            headless = defaultHeadless =
                                Boolean.valueOf(("Linux".equals(osName) ||
                                                 "SunOS".equals(osName) ||
                                                 "FreeBSD".equals(osName) ||
                                                 "NetBSD".equals(osName) ||
                                                 "OpenBSD".equals(osName)) &&
                                                 (System.getenv("DISPLAY") == null));
                        }
                    }
               } else if (nm.equals("true")) {
                    headless = Boolean.TRUE;
                } else {
                    headless = Boolean.FALSE;
                }
                return null;
            }
            }
        );
    }
    return headless.booleanValue();
}

When DISPLAY variable is set java determines that it is NOT headless, which means it will try to establish connection to X11 Server (which will be disconnected when the user closes their session causing GraphicsEnvironment to fail to start)

Conclusion –

You have three options

  1. Start your application with the -Djava.awt.headless=true
  2. Start your application with DISPLAY unset.
  3. Make sure no-one uses a putty manager that automagically sets X11 DISPLAY variable.

Personally I would use #1 -Djava.awt.headless=true

Question:

Following this example, the compiler is complaining that setFontSize is deprecated (I'm using Scala). How to set the font size?

   val normalStyle = new JRDesignStyle
   boldStyle.setFontName("DejaVu Sans")
   boldStyle.setFontSize(12)  // <--- this is deprecated

Answer:

As you can see, this method replaced with new one: JRDesignStyle.setFontSize(Float).

In Java your code will be:

boldStyle.setFontSize(12.0f);

Question:

I'm trying to set font on JRDesignTextField object of a JasperReport from the one I have in JasperDesign's getFontMap() like this:

JRDesignTextField text; // I have this object
JasperDesign jasperDesign; //I have this object from a master jrxml template
text.setFont((JRFont)jasperDesign.getFontsMap().get("ColumnHeadingFont"));

Upgrading to JasperReports 6.0.3, setting font on JRDesignTextField and getting FontMap from JasperDesign are flagged as "deprecated". After digging in little bit, I did some workaround to adapt this code which I'm not sure is correct:

JRDesignTextField text; // I have this object
JasperDesign jasperDesign; //I have this object
text.setFontName("ColumnHeadingFont");
List<JRStyle> stylesList = jasperDesign.getStylesList();
for(JRStyle st : stylesList){
    if("ColumnHeadingFont".equals(st.getFontName()))
    {
        text.setFontSize(st.getFontsize());
        break;
    }
}

So the problem is divided into two:

  • Get FontMap from JasperDesign object

  • Setting Font on JRDesignTextField (which I'll get from FontMap)

Is the way I proposed for this problem is correct and if there is any better way to do that?


Answer:

So the solution I proposed is the feasible one... To get the style details(font size) from the master template and use that in designing new template's textField. Code is as below:

JRDesignTextField text; // This text field is from the new jasper report file
JasperDesign jasperDesign; //This is from master template that has all info

text.setFontName("ColumnHeadingFont");
List<JRStyle> stylesList = jasperDesign.getStylesList();
for(JRStyle st : stylesList){
if("ColumnHeadingFont".equals(st.getFontName()))
{
    text.setFontSize(st.getFontsize());
    break;
}
}

Question:

We have a WebService that receives a name of a report and its parameters, and calls a JRPdfExporter.

Since the reports can be added at runtime by the users, we need a way to dynamically add the Jasper jar font file into the classpath, so that any new fonts added by the users can be added to Jasper classpath.

We are using this code to try and inject the new jar into the classpath but with no success

URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;

try {
    Method method = sysclass.getDeclaredMethod("addURL", new Class[]{URL.class});
    method.setAccessible(true);
    method.invoke(sysloader, new Object[] {u});
} catch (Throwable t) {
    t.printStackTrace();
    throw new IOException("Error, could not add URL to system classloader");
}

The error I'm getting is

net.sf.jasperreports.engine.JRRuntimeException: net.sf.jasperreports.engine.JRException: Input stream not found at : fonts/fontsfamily1415192514231.xml

Any ideas on how I can do this?

Or is there a better alternative?


Answer:

I solved this problem by "simpling" searching a specific folder for JAR font files, opening them and uncompressing all the necessary files/folders.