rss
Affichage des articles dont le libellé est Java. Afficher tous les articles
Affichage des articles dont le libellé est Java. Afficher tous les articles

HowTo: Swig C to Java: functions that manage an array of int, simple or complex struct (eg. array of objects)

You would like to map a C code from your Java application and you have heard about Swig: this post is for you!

This post is a really simple example to understand how to manage (from Java application) a C array of complex structure.

After reading Swig 3 documentation and searching on the net, I've finally succeed to create a little poc on how to return a list of struct.

proof of concept sample includes 4 functions:
  • a function sumitems that accept an array of integer as parameter to calculate a sum (part of Swig3 documentation)
  • a function populateSampleItem that just write on a given simple structure
  • a function populateItems that update an existing array of struct
  • a function buildItems that create from scratch a result array of struct

source of this poc is available here : https://github.com/boly38/pocswig
this poc is widely inspirated from similar example from "Samuel Jacob's Weblog" post (thanks to him !)


First file to write is the C header poc.h:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* (in) simple int array as parameter */
int sumitems(int *first, int nitems);

/* simple structure */
typedef struct MyItem_t {
      int  id;
      char *name;
} MyItem;

/* (in/out) simple struct as parameter (updated by the function) */
void populateSampleItem(MyItem *item);

/* array of structure */
typedef struct MyItems_t {
      int  count;       // elements count
      MyItem *elements; // array of MyItem
} MyItems;

/* (in/out) array of structure as parameter (updated by the function) */
void populateItems(MyItems *items);

/* (out) array of structure (generated by the function) */
MyItems *buildItems();
The you will then have to write the C implementation. Here is a sample poc.c:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "poc.h"

int sumitems(int *first, int nitems) {
  int i, sum = 0;
  for (i = 0; i < nitems; i++) {
    sum += first[i];
  }
  return sum;
}

void populateSampleItem(MyItem *item) {
  item->id = 1234;
  item->name = strdup("getSampleItem");
}

void populateItems(MyItems *items) {
    int nb = items->count;
    items->elements = malloc(nb * sizeof(MyItem));
    for (int j=nb-1;j>=0;j--) {
        items->elements[j].id = j;
        char elementName[80];
        sprintf(elementName, "populateItems %d", j);
        items->elements[j].name = strdup(elementName);
    }
}

MyItems *buildItems() {
    printf("buildItems 14 elements");
    int nb = 14;
    MyItems *items= malloc(sizeof(MyItems));;
    items->count = nb;
    items->elements = malloc(nb * sizeof(MyItem));
    for (int j=nb-1;j>=0;j--) {
        items->elements[j].id = j;
        char elementName[80];
        sprintf(elementName, "buildItems %d", j);
        items->elements[j].name = strdup(elementName);
    }
    return items;
}

And now to access this function from java, you will have to use Swig.

Swig use a specification file to setup how to map function/types/etc... This file is a .i file. Here is the file poc.i:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
%module swigpoc

%include "arrays_java.i";
%apply int[] {int *};

%{
 #include "poc.h"
%}

%include "poc.h";
%extend MyItems_t{
  MyItem * getElement(int i) {
      return &$self->elements[i];
  }
}
Line 1 define the module name,
Line 3&4 define how to handle int array using swig facility,
Line 6 to 8 to tell to Swig to output include line into the target wrapper file.
Line 10 reuse as is the header file as specification (Swig MUST wrap all header file methods and structs to Java).
Line 11 to 15 to tell to Swig to append an extra function to help Java user to access to array element.

Now you will have to generate Java files! Use Swig :
rm -f *Item.java *.o *.dll swigpoc*
swig -java poc.i
You could look at your directory, there is some new C and Java files: poc_wrap.c, MyItem.java, MyItems.java, swigpoc.java swigpocJNI.java Next step is to build up the DLL (shared library) (exemple under Cygwin):
1
2
3
4
5
6
#!/bin/bash
JAVA_HOME=/cygdrive/c//Programmes/Java/jdk1.8.0_112/
INCLUDES="-I$JAVA_HOME/include/ -I$JAVA_HOME/include/win32/"

x86_64-w64-mingw32-gcc.exe -c poc.c poc_wrap.c $INCLUDES
x86_64-w64-mingw32-gcc.exe $INCLUDES -shared -o poc.dll poc_wrap.o poc.o
Now you can play with your new library from Java; exemple PocExample.java:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class PocExample {

  static {
    System.out.println("load poc ...");
    System.loadLibrary("poc");
    System.out.println("load poc ... OK ");
  }

  public static void  main(String args[]) {
    System.out.println("poc");

    System.out.println("sumitems:");
    int[] arrayB = new int[10000000];          // Array of 10-million integers
    for (int i=0; i<arrayB.length; i++) {      // Set some values
      arrayB[i] = i;
    }
    int sum = swigpoc.sumitems(arrayB, 10000);
    System.out.println("SumB = " + sum);


    System.out.println("MyItem:");
    MyItem myt = new MyItem();
    swigpoc.populateSampleItem(myt);
    System.out.println("myt.name = " + myt.getName());

    MyItems myts = new MyItems();
    myts.setCount(10);
    swigpoc.populateItems(myts);

    for (int j=0; j<myts.getCount(); j++) {
      System.out.println(String.format("myts.element[%d].name = '%s'",j, myts.getElement(j).getName()));
      System.out.println(String.format("myts.element[%d].id = '%s'"  ,j, myts.getElement(j).getId()));
    }


    MyItems rez = swigpoc.buildItems();
    for (int k=0; k<rez.getCount(); k++) {
      System.out.println(String.format("rez.element[%d].name = '%s'",k, rez.getElement(k).getName()));
      System.out.println(String.format("rez.element[%d].id = '%s'"  ,k, rez.getElement(k).getId()));
    }
  }
}

I let you execute that:
1
2
3
#!/bin/bash
JAVA_HOME=/cygdrive/c//Programmes/Java/jdk1.8.0_112/
$JAVA_HOME/bin/javac *.java && $JAVA_HOME/bin/java PocExample


If you see something wrong, please tell me. Else hopes this helps!

Log4j 1.x with maven: minimal howto

This post is a minimal howto for log4j version 1.

What a simple task ! but who never search a simple sample on the web send a little stone to me ;)
Feel free to copy paste anyway ;))

What are the dependencies

You will need to add log4j, slf4j and lombok (to use @Slf4j) :

 <properties>
  <!--General project configuration -->
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

  <lombok.version>1.16.6</lombok.version>
  <log4j.version>1.2.17</log4j.version>
  <slf4j.version>1.7.10</slf4j.version>
  <junit.version>4.12</junit.version>
 </properties>
 <dependencyManagement>
  <dependencies>
   <!-- @Slf4j annotation -->
   <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    <scope>provided</scope>
   </dependency>
   <!-- ## LOGS ## -->
   <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>${log4j.version}</version>
   </dependency>
   <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j.version}</version>
   </dependency>
   <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>${slf4j.version}</version>
   </dependency>

How to use it

Simply add "@Slf4j" to your class, and then you could use "log" as logger.

import java.security.InvalidParameterException;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyClass {
    public MyClass() {
        log.debug("constructor YEP");
    }
    
    public void doIt(long value) {
        log.info("doIt {}", value);
        try {
            throw new InvalidParameterException("Zobby");
        } catch (Exception ee) {
            log.error("I'm so stupid: {} => {}", ee.getClass().getSimpleName(), ee.getMessage(), ee);
            log.warn("same without stack trace !: {} => {}", ee.getClass().getSimpleName(), ee.getMessage());
        }
    }
}

How to configure appenders

To configure log4j, add a log4j.properties file to your resources. Start by defining a console appender. This appender should appear in the rootLogger definition too. You could define the following content to start:

log4j.rootLogger=DEBUG, console

## Console appender 
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.threshold=DEBUG
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d %5p [%c{1}] %m%n

# package log levels
#
# Log4j bootstrap
#
log4j.logger.org.apache.http = INFO
log4j.logger.com.myapp = DEBUG
#

How to configure file appender

Update your rootLoger to refer to the logfile appender, and add a logfile appender definition:

log4j.rootLogger=DEBUG, console, logfile

## File appender
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.threshold=DEBUG
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{ISO8601} - %-5.5p - %t - %c - %m%n
log4j.appender.logfile.file=MY_CUSTOMFILEHERE.log
log4j.appender.logfile.maxBackupIndex=5
log4j.appender.logfile.maxFileSize=2048KB
log4j.appender.logfile.append=true

How to configure graylog (splunk-like) appender

Update your rootLoger to refer to the graylog appender, and add a gelf appender definition:

### Graylog appender
log4j.appender.graylog2=org.graylog2.log.GelfAppender
log4j.appender.graylog2.graylogHost=tcp:mygraylogserver.net
log4j.appender.graylog2.graylogPort = 12201
#log4j.appender.graylog2.originHost=(default to the local hostname)
log4j.appender.graylog2.facility=gelf-java
log4j.appender.graylog2.layout=org.apache.log4j.PatternLayout
log4j.appender.graylog2.extractStacktrace=true
log4j.appender.graylog2.addExtendedInformation=true
log4j.appender.graylog2.additionalFields={'environment': '${projectEnvironment}', 'application': 'MyPoc','version': '${buildVersion}_${buildTimestamp}'}

How to configure NT event appender

Pre-requisites : add NTEventLogAppender.dll and NTEventLogAppender.amd64.dll to the current directory or to the java ld library path. You could append "-Djava.library.path=PATH" to your application if needed. Get this dll files from log4j zip distribution  doc : https://wiki.apache.org/logging-log4j/NTEventLogAppender

Update your rootLoger to refer to the ntappender appender, and add a new ntappender definition:

# NTEventLogAppender
log4j.appender.ntappender=org.apache.log4j.nt.NTEventLogAppender
log4j.appender.ntappender.Source=MyKillerApp
log4j.appender.ntappender.layout=org.apache.log4j.PatternLayout
log4j.appender.ntappender.layout.ConversionPattern=%d{ISO8601} - %-5.5p - %t - %c - %m%n

For this last usecase you will be able to see the app logs into Windows Event Log service :

gradle: fiche pratique

Cet article est une fiche pratique pour Gradle.

Depuis Windows

  • Derrière un proxy : éditer gradlew.bat et modifier la ligne suivante pour positionner votre proxy
set DEFAULT_JVM_OPTS=-Dhttp.proxyHost=myproxy.com -Dhttp.proxyPort=8080
  • liste des tâches
./gradlew.bat tasks
  • build
./gradlew.bat build
  • build sans test (traduire: exécuter la tâche 'build' sans exécuter la tâche 'test')
./gradlew.bat build -x test
  • build sans test ni javadoc
./gradlew.bat build -x test -x javadoc

Unit test of a spring scheduler configuration using Mockito.spy

Hello,

here is a little sample on how to write scheduled monitoring unit test using spring and mockito spy factory method.


In the legacy code there is a job that is defined to purge the database each 8 hour using a spring defined quarrtz job:


scheduled-service-context.xml


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns="http://www.springframework.org/schema/beans"
          xsi:schemalocation="http://www.springframework.org/schema/beans  
               http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">  
   <bean class="com.foo.ScheduledDatabasePurgeMonitor"
            id="scheduledDatabasePurgeMonitor">  
 </bean></beans>  

scheduled-trigger-context.xml

 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans  
               http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">  
   <bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" id="scheduledDatabasePurgeJob">  
     <property name="targetObject" ref="scheduledDatabasePurgeMonitor">  
     <property name="targetMethod" value="doIt">  
     <property name="concurrent" value="false">  
   </property></property></property></bean>  
   <bean class="org.springframework.scheduling.quartz.SimpleTriggerBean" id="databasePurgeTrigger">  
     <!-- see the example of method invoking job above -->  
     <property name="jobDetail" ref="scheduledDatabasePurgeJob">  
     <!-- first start after 1 milliseconds -->  
     <property name="startDelay" value="1">  
     <!-- repeat every (schedule.databasePurge.repeatIntervalMs) milliseconds / default 8 hours -->  
     <property name="repeatInterval" value="${schedule.databasePurge.repeatIntervalMs:28800000}">  
   </property></property></property></bean>  
   <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
     <property name="triggers">  
       <list>  
         <ref bean="databasePurgeTrigger">  
       </ref></list>  
     </property>  
   </bean>  
   <!-- official howto :  
      http://static.springsource.org/spring/docs/3.0.5.RELEASE/reference/scheduling.html#scheduling-quartz  
    -->  
 </beans>  

here is the sample purgemonitor class:


 /**  
  * ScheduledDatabasePurgeMonitor  
  */  
 public class ScheduledDatabasePurgeMonitor {  
   private final static Logger logger = LoggerFactory.getLogger(ScheduledDatabasePurgeMonitor.class.getName());  
   public void doIt() {  
     logger.debug("database purge right now");  
     purgeDatabase();  
   }  
   void purgeDatabase() {  
     logger.debug("* purge database *");  
   }  
 }  

we would like to ensure in an unit test that the spring configuration enable the purge scheduling each N ms :

  • we would like a little N to avoid an 8 hour unit test
  • we would like to mock the purge service


So we need a sping spy'ed bean and here is a way to do this :
Unit test class :


 package com.foo;  
 import org.junit.Assert;  
 import org.junit.Before;  
 import org.junit.Ignore;  
 import org.junit.Test;  
 import org.junit.runner.RunWith;  
 import org.slf4j.Logger;  
 import org.slf4j.LoggerFactory;  
 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.test.context.ContextConfiguration;  
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
 import static org.mockito.Mockito.spy;  
 import static org.mockito.Mockito.times;  
 import static org.mockito.Mockito.verify;  
 /**  
  * ScheduledDatabasePurgeMonitorTest IT Test of the database purge monitoring  
  */  
 @RunWith(SpringJUnit4ClassRunner.class)  
 @ContextConfiguration  
 public class ScheduledDatabasePurgeMonitorTest {  
      private final static Logger logger = LoggerFactory.getLogger(ScheduledDatabasePurgeMonitorTest.class.getName());  
   @Autowired  
   ScheduledDatabasePurgeMonitor scheduledDatabasePurgeMonitor;  
   @Autowired  
   private Integer purgeRepeatIntervalMs;
   @Before
   public void setup() {
       // avoid the database purge here
       doNothing().when(scheduledDatabasePurgeMonitor).purgeDatabase();
   }  
      /**  
       * GIVEN ScheduledDatabasePurgeMonitor implementation WHEN scheduler  
       * trigger THEN purge service is called  
       *   
    */  
      @Test  
      public void database_purge_monitor_should_be_periodically_invoked() {  
     // THEN  
     logger.info("verify first call of purgeDatabase");  
     int callNumber = 1;  
     verify(scheduledDatabasePurgeMonitor, times(callNumber)).purgeDatabase();  
     for (int i=0; i<3 ; i++) {  
       waitForNextScheduledUpdate();callNumber++;  
       logger.info("verify next call ({}) of purgeDatabase", callNumber);  
       verify(scheduledDatabasePurgeMonitor, times(callNumber)).purgeDatabase();  
     }  
      }  
   private void waitForNextScheduledUpdate() {  
     try {  
       long waitIntMs = getPurgeRepeatIntervalMs() + 100;  
       logger.debug("wait for {} seconds", (waitIntMs / 1000));  
       Thread.sleep(waitIntMs);  
     } catch (InterruptedException e) {  
       Assert.fail("InterruptedException oO");  
     }  
   }  
   public ScheduledDatabasePurgeMonitor getScheduledDatabasePurgeMonitor() {  
     return scheduledDatabasePurgeMonitor;  
   }  
   public void setScheduledDatabasePurgeMonitor(ScheduledDatabasePurgeMonitor scheduledDatabasePurgeMonitor) {  
     this.scheduledDatabasePurgeMonitor = scheduledDatabasePurgeMonitor;  
   }  
   public Integer getPurgeRepeatIntervalMs() {  
     return purgeRepeatIntervalMs;  
   }  
   public void setPurgeRepeatIntervalMs(Integer purgeRepeatIntervalMs) {  
     this.purgeRepeatIntervalMs = purgeRepeatIntervalMs;  
   }  
 }  

Unit test spring context :

 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">  
   <import resource="classpath:/META-INF/spring/jasypt-context.xml"/>  
   <bean class="org.jasypt.spring31.properties.EncryptablePropertySourcesPlaceholderConfigurer" depends-on="jasyptPrerequisites">  
     <constructor-arg ref="configurationEncryptor"/>  
     <property name="locations">  
       <list>  
         <value>classpath:credentials.properties</value>  
         <value>classpath:/com/foo/scheduled-database-purge.properties</value>  
       </list>  
     </property>  
   </bean>  
   <bean id="toSpyScheduledDatabasePurgeMonitor"  
      class="com.foo.ScheduledDatabasePurgeMonitor"/>  
   <bean id="scheduledDatabasePurgeMonitor"  
      class="org.mockito.Mockito" factory-method="spy">  
     <constructor-arg ref="toSpyScheduledDatabasePurgeMonitor" />  
   </bean>  
   <import resource="classpath:META-INF/spring/services-scheduled-trigger-context.xml"/>  
   <bean id="purgeRepeatIntervalMs" class="java.lang.Integer">  
     <constructor-arg value="${schedule.databasePurge.repeatIntervalMs}"/>  
   </bean>  
 </beans>  

unit test properties file :

# test purpose : ask iaas quota each 2 seconds
schedule.databasePurge.repeatIntervalMs=2000

you would notice that Mockito.spy(object) static method is used into the spring unit test context :

   <bean id="scheduledDatabasePurgeMonitor"  
      class="org.mockito.Mockito" factory-method="spy">  
     <constructor-arg ref="toSpyScheduledDatabasePurgeMonitor" />  
   </bean>  

Selenium Acceptance/Integration Tests for a Google App Engine application using maven-gae-plugin

Hi,
 this is just a little tip on how to setup selenium acceptance tests for a Google App Engine java application when using maven-gae-plugin.
  • First, be sure you are using an earlier version (> 0.9.6 release ; eg. the latest snapshot version) of maven-gae-plugin.
    Here is an extract of a pom for the plugin repository and selenium deps right here :

  <properties>  
     <selenium.version>2.15.0</selenium.version>  
     <maven-gae-plugin.version>0.9.7-SNAPSHOT</maven-gae-plugin.version>  
   </properties>  


 <pluginrepositories>  
     <pluginrepository>  
       <id>sonatype.snapshots</id>  
       <url>https://oss.sonatype.org/content/repositories/snapshots/</url>  
       <releases><enabled>true</enabled></releases>  
       <snapshots><enabled>true</enabled></snapshots>  
     </pluginrepository>  
 </pluginrepositories>  
     <!-- acceptance tests -->  
     <dependency>  
       <groupid>org.seleniumhq.selenium</groupid>  
       <artifactid>selenium-java</artifactid>  
       <version>${selenium.version}</version>  
       <scope>test</scope>  
     </dependency>  

  • Then, map the gae-start and gae-stop executions to the pre/post integration phases. This is an example of the plugin configuration :
      <plugin>  
         <groupid>net.kindleit</groupid>  
         <artifactid>maven-gae-plugin</artifactid>  
         <version>${maven-gae-plugin.version}</version>  
         <configuration>  
           <unpackversion>${gae.version}</unpackversion>  
           <serverid>appengine.google.com</serverid>  
           <appdir>${webappDirectory}</appdir>  
         </configuration>  
         <executions>  
           <execution>  
             <id>unpack</id>  
             <phase>validate</phase>  
             <goals>  
               <goal>unpack</goal>  
             </goals>  
           </execution>  
           <execution>  
             <id>deploy</id>  
             <goals>  
               <goal>deploy</goal>  
             </goals>  
           </execution>  
           <execution>  
             <id>gae-start</id>  
             <phase>pre-integration-test</phase>  
             <goals>  
               <goal>start</goal>  
             </goals>  
             <configuration>  
               <appdir>${webappDirectory}</appdir>  
               <port>9000</port>  
               <address>0.0.0.0</address>
               <sdkdir>${gae.home}</sdkdir>  
               <wait>false</wait>  
               <disableupdatecheck>true</disableupdatecheck>  
             </configuration>  
           </execution>  
           <execution>  
             <id>gae-stop</id>  
             <phase>post-integration-test</phase>  
             <goals>  
               <goal>stop</goal>  
             </goals>  
           </execution>  
         </executions>  
         <dependencies>  
           <dependency>  
             <groupid>net.kindleit</groupid>  
             <artifactid>gae-runtime</artifactid>  
             <version>${gae-runtime.version}</version>  
             <type>pom</type>  
           </dependency>  
         </dependencies>  

  • The next and last step is to write your acceptance test. Here is just a simple example :
 package net.mademocratie.gae.acceptancetest;  
 import org.junit.After;  
 import org.junit.Before;  
 import org.junit.Test;  
 import org.openqa.selenium.WebDriver;  
 import org.openqa.selenium.firefox.FirefoxDriver;  
 import static com.thoughtworks.selenium.SeleneseTestBase.assertEquals;  
 public class HomePageIT {  
   private WebDriver driver;  
   @Before  
   public void setUp() {  
     driver = new FirefoxDriver();  
   }  
   @Test  
   public void home_page_should_be_displayed() {  
     this.driver.get("http://localhost:9000");  
     assertEquals("Ma Democratie", this.driver.getTitle());  
   }  
   @After  
   public void tearDown() {  
     driver.quit();  
   }  
 }  

You should find a full running sample on the github repository of ma-dem-ae.
NB: (possible) related issue #5
PS: thanks to codeformatter for this post ;)
PS(bis): oO i just heard that CasperJS or cucumber.js could do the job too...

Axis : No serializer found for class java.lang.Class

Juste un petit post (encore bien moins intéressant que le précédent!) pour mémoire...

Lorsqu'on tente de sérialiser un objet Java avec un serveur Axis 1.4 (pour un Web Service);

on peut tomber sur l'exception suivante :

13:47:33 WARN AttachmentsImpl: Exception:
AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode:
faultString: java.io.IOException: No serializer found for class java.lang.Class in registry org.apache.axis.encoding.TypeMappingDelegate@16e22da
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}stackTrace:java.io.IOException: No serializer found for class java.lang.Class in registry org.apache.axis.encoding.TypeMappingDelegate@16e22da
at org.apache.axis.encoding.SerializationContext.serializeActual(SerializationContext.java:1507)
at org.apache.axis.encoding.SerializationContext.serialize(SerializationContext.java:980)
at org.apache.axis.encoding.SerializationContext.outputMultiRefs(SerializationContext.java:1055)
at org.apache.axis.message.SOAPBody.outputImpl(SOAPBody.java:145)
at org.apache.axis.message.SOAPEnvelope.outputImpl(SOAPEnvelope.java:478)
at org.apache.axis.message.MessageElement.output(MessageElement.java:1208)
at org.apache.axis.SOAPPart.writeTo(SOAPPart.java:315)
at org.apache.axis.SOAPPart.writeTo(SOAPPart.java:269)
at org.apache.axis.SOAPPart.saveChanges(SOAPPart.java:530)
at org.apache.axis.attachments.AttachmentsImpl.getAttachmentCount(AttachmentsImpl.java:554)
at org.apache.axis.Message.getContentType(Message.java:486)
at org.apache.axis.transport.http.AxisServlet.doPost(AxisServlet.java:775)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
at org.apache.axis.transport.http.AxisServletBase.service(AxisServletBase.java:327)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:172)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:433)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:174)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:874)
at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:665)
at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:528)
at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:81)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:689)
at java.lang.Thread.run(Thread.java:636)


Une recherche Google sur la phrase exacte "No serializer found for class java.lang.Class" ne donnant qu'un seul résultat peu explicite, je note ici pourquoi j'ai rencontré cette erreur.

Généralement, si c'est une classe ordinaire que le serveur n'a pas réussi à sérialiser, c'est que la définition du type est manquante dans le descripteur de déploiement (fichier .wsdd). Mais ici, l'erreur indique que le serveur n'arrive pas à sérialiser un objet "Class" !

Dans mon cas de figure, l'objet que je tentait de sérialiser comportait une énumération. Et voici, ci dessous comment rédiger un "Custom Serializer" pour une Enum Java :

/**
* Get Custom Serializer
*/
public static org.apache.axis.encoding.Serializer getSerializer(
java.lang.String mechType,
java.lang.Class _javaType,
javax.xml.namespace.QName _xmlType) {
log.debug("getSerializer(...)");
return
new org.apache.axis.encoding.ser.EnumSerializer(_javaType, _xmlType); // , typeDesc);
// new org.apache.axis.encoding.ser.BeanSerializer(_javaType, _xmlType, typeDesc);
}

/**
* Get Custom Deserializer
*/
public static org.apache.axis.encoding.Deserializer getDeserializer(
java.lang.String mechType,
java.lang.Class _javaType,
javax.xml.namespace.QName _xmlType) {
return
new org.apache.axis.encoding.ser.EnumDeserializer(_javaType, _xmlType);
// new org.apache.axis.encoding.ser.BeanDeserializer(_javaType, _xmlType, typeDesc);
}


Vous remarquerez que la ligne en commentaire correspond à un "Serializer" de Classe Java, ici remplacé par un "Serializer" pour une Enum.




Ajout du 15/07/2010 : après avoir reçu ce jour une question sur comment sérialiser un objet comportant un type Enum (JDK 5), j'ai regardé le projet sur lequel j'avais travaillé à l'époque.

Et .. j'étais finalement revenu sur l'utilisation du type Enum : semble-t-il un type trop "spécifique" pour le traitement générique par Axis. J'avais alors remplacé les classes type Enum par des classes simples (donc utilisé au final un BeanSerializer).

Tout çà pour dire que l'exemple cité dans le post ci-dessus n'est certainement pas le mieux choisi (si je reçois un jour la solution, je l'ajouterai..).

Tomcat et la fameuse "OutOfMemoryError : PermGen space"


Qu’appelle-t-on "OutOfMemoryError : PermGen space" ?



Cette erreur se rencontre lorsque l'espace de la mémoire permanente (PermGen) de la machine virtuelle Java (JVM) a atteint son niveau maximum (ie. sur une JVM Sun).

Les définitions de classe sont stockées au sein de la mémoire permanente. Cette erreur est levée lorsque l'espace restant n'est plus suffisant pour accueillir les nouvelles définitions de classe à charger.

NB: les instances sont stockées dans une zone mémoire différente ("Heap" space) qui correspond à une autre erreur.


Quand rencontre-t-on une "OutOfMemoryError : PermGen space" ?



On rencontre par exemple cette erreur après de multiples redéploiements d'une WebApp Tomcat comportant des "fuites" mémoire... On va redéployer la WebApp 10 fois sans problème, puis on perd Tomcat à la 11ième à cause de cette erreur. Bien souvent cette erreur pose de sérieux problèmes en production où la tendance est plutôt à redémarrer Tomcat tous les soirs pour éviter tout problème (plutôt que de corriger les soucis de fuites).


Un excellent papier rédigé par Franck Kieviet (en anglais, mais très visuel ;) ) explique comment se provoque une fuite mémoire (ie. déchargement impossibles d'objets/de définitions de classes) et cite les exemples les plus courants de fuites mémoire :
   Dangling thread, Commons logging, java.util.logging.Level, Thread context classloader, new Thread(), Bean util, Details, SQL Driver


Quels sont les symptômes d'une "OutOfMemoryError : PermGen space" ?



Peut-être ne l'avez-vous jamais rencontré, alors comment savoir si votre WebApp est sujette aux fuites de mémoire (donc à terme à cette erreur) ?


Des outils permettent de visualiser l'occupation des différents espaces mémoire :

  • JConsole : application visuelle qui se rattache à une JVM (un process Java) et qui (entre autres nombreuses fonctionnalités) affiche les courbes : espace occupé par les données (heap), nombre de définitions de classes actuellement en mémoire, total du nombre de classes chargées et déchargées depuis le lancement de JConsole, nombre de thread, etc..

  • jmap : un outil qui se rattache également à une JVM et qui est capable (entre autres) de faire des extractions de la mémoire pour en ressortir des statistiques (ie. liste des définitions de classes en mémoire). Jmap permet également de faire un dump de cette mémoire pour jhat.

  • jhat : un outil capable de lire un dump créé par jmap et de créer un mini serveur Web pour naviguer dans cette mémoire (utilisateurs avertis!!).




Pour visualiser le problème, je conseille JConsolearticle de sun (en).

Mais comment utiliser JConsole ?



Il faut autoriser JConsole à pouvoir se rattacher à la JVM à osculter.

Comment ? voici un exemple :
 ajoutez les options suivantes à la JVM de votre application (fonctionne aussi pour Tomcat) :

-Dcom.sun.management.jmxremote.port=9090
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false


Déterminez ensuite quel est le numéro du processus Java correspondant à votre application (Tomcat correspond à la ligne jps "Bootstrap") :

$ ps -xaf | grep java
ou
$ jps



Une fois le numéro de processus récupéré, lancer JConsole (sous un environnement graphique, avec un utilisateur autorisé, (ie. tomcat)...) :

# jconsole &

Choisissez le menu "New" / "Connexion" puis le processus correspondant à votre application.


Voici un aperçu de JConsole pour plusieurs déploiements d'une WebApp comportant une fuite mémoire (ou mal paramétrée) :



   on peut constater (encadré en bas à gauche : mémoire permanente), après de nombreux redéploiements, qu'aucune classe n'a été déchargée. Ce genre de comportement se termine par une erreur.


Voici un aperçu de JConsole pour plusieurs déploiements d'une WebApp ne comportant plus de fuite mémoire (amha) :

   on peut constater (encadré en bas à gauche : mémoire permanente), après de nombreux redéploiements, des classes ont été déchargées.

   A partir du moment où, malgrès le nombre de redéploiements, la courbe forme une sorte de plateau (nombre de classes maximum impossible à dépasser, ici 16000 classes), on peut supposer que la WebApp est "stable" au niveau occupation de la mémoire permanente.

   PS: il semblerai que sur la SDK de Sun, le déchargement de classe ne soit pas systématique, mais pratiqué que lorsque nécessaire. Cela expliquerai pourquoi le déchargement de classe n'intervient pas lors des tous premiers redéploiements (ie, dans mon cas j'ai constaté une dizaine de redéploiements).



Comment se protéger de l'"OutOfMemoryError : PermGen space" ?



C'est bien ici la question la plus intéressante de ce post...

Voici une liste (non-exhaustive) des moyens de se protéger de cette erreur :

  • Tenter d'identifier les librairies / classes utilisées qui ont des fuites de mémoire : solution la plus satisfaisante, évidemment, mais qui peut prendre beaucoup de temps...

  • Utiliser un autre SDK (ie.IBM) : sans le désir de polémiquer, j'ai déjà remarqué et lu à plusieurs reprises que ce problème était moins fréquent sur une autre JVM ... : solution assez peu satisfaisante

  • Augmenter la mémoire allouée : votre serveur ne mettra plus 3j à tomber mais 5 ou 10 ? : voici solution pour les rois de la bricole (marche aussi pour les hommes pressés) ...

  • Isoler la WebApp dans une JVM dédié: (post sur le sujet) ce point n'est pas réellement une mesure de protection sur l'application elle-même, mais peut être une très bonne mesure à prendre pour éviter de contaminer plusieurs applications à cause d'une seule qui pose problème...



Identifier les fuites mémoires



Après plusieurs essais avec jmap et jhat, et navigation dans le dump de la mémoire, j'ai perdu un peu mes espoirs de trouver une solution toute faite pour identifier les fuites mémoire.

En réalité, jmap sera pour moi amplement suffisant..


En préambule : lancez votre WebApp et effectuez plusieurs redéploiements (s'arrêter avant l'exception PermGen).

Sous réserve d'un paramétrage et d'un utilisateur correct (cf exemple de JConsole ci-dessus) la commande suivante affiche les objets chargés en mémoire et enregistre le résultat dans le fichier JVM_MEMORY.

jmap -histo PID_OF_TOMCAT > JVM_MEMORY


Suivant le nombre d'objet, la liste retournée peut être très longue.

Pour chaque définition de classe présente en mémoire, 4 colonnes sont affichées sur une ligne : un id, un nombre d'instances, une taille occupée (bytes), le nom de la classe.

Il s'agit de repérer les fuites en isolant les répétitions trop nombreuses de définition de classes (plusieurs lignes avec un même nom de classe).


Voici des exemples de définitions multiples rencontrées :

com.mysql.jdbc.VersionedStringProperty
com.mysql.jdbc.SingleByteCharsetConverter
com.mysql.jdbc.Driver
com.mysql.jdbc.log.NullLogger
apache.log4j.helpers.NullEnumeration
org.apache.log4j.DefaultCategoryFactory
org.apache.log4j.or.RendererMap
org.apache.log4j.spi.RootLogger
org.apache.log4j.Hierarchy
org.apache.log4j.Level
org.apache.log4j.ProvisionNode
org.apache.log4j.CategoryKey
org.apache.log4j.Logger
org.apache.commons.dbutils.BasicRowProcessor



Pour constater un problème de fuite mémoire avec MySQL par exemple, voici une méthode que j'ai utilisé :

jmap -histo PID_OF_TOMCAT | grep "com.mysql.jdbc.Driver" | wc -l

Cette commande affiche le nombre de définition en mémoire pour la classe Driver.

J'effectue un redéploiement de la WebApp et je rejoue la même commande.

jmap -histo PID_OF_TOMCAT | grep "com.mysql.jdbc.Driver" | wc -l


Si le nombre retourné augmente d'un, alors c'est qu'aucune classe n'a été déchargée.


Libérer la mémoire !


Voici les différentes solutions que j'ai utilisé pour libérer de la mémoire permanente.

Tout d'abord, et c'est essentiel, il faut autoriser la JVM à décharger des classes. Ajouter l'option suivante à votre application :

-XX:+CMSClassUnloadingEnabled


Ensuite, vous pouvez paramétrer la taille max de votre mémoire permanente (par défaut à 64M je crois, et n'oubliez pas l'unité "M"):

-XX:MaxPermSize=80M


Ensuite, libérez la mémoire en détruisant notamment :

  • les drivers JDBC montés en mémoire

  • les threads non terminés

  • les singletons

  • ...



Vous pourrez pour cela ajouter un listener à votre WebApp (cf. java-tips (en)) et libérer la mémoire lorsque le contexte est détruit ("contextDestroyed(ServletContextEvent event)").

Sur le wiki de Tomcat, le problème est abordé et certaines méthodes sont également évoquées.


Voici les méthodes que j'ai pu retenir.

Exemple pour libérer les singletons :

if (MySingleton.isInstance()) {
MySingleton.getInstance().destroy();
}


Exemple pour libérer "common-logging" :

// cf. http://wiki.apache.org/jakarta-commons/Logging/FrequentlyAskedQuestions
System.out.println("Shutdown LogFactory (common-logging) !");
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
LogFactory.release(contextClassLoader);


Exemple pour libérer "log4j" :

// cf. http://wiki.apache.org/logging-log4j/UsefulCode
System.out.println("Shutdown LogManager (log4j) !");
LogManager.shutdown();



Exemple pour libérer les drivers JDBC :

Enumeration loadedDrivers = java.sql.DriverManager.getDrivers();
while (loadedDrivers!= null && loadedDrivers.hasMoreElements()) {
Driver d = loadedDrivers.nextElement();
log.info("Unregister Driver " + d.toString());
try {
DriverManager.deregisterDriver(d);
} catch (SQLException sqle) {
log.error("Exception while unregister Driver: " + sqle.getMessage());
}
}




Merci de me faire vos remarques si ce sujet comporte des erreurs.. si vous voyez d'autres astuces je les ajoute.

Mots clés du blog

10.1 4G acceptancetest adb androï Android androïd Android7 api appender appengine application applications archive array assistantematernelle astuce auth0 authentication authority automation Axis bash bearer blog boot bootloader bower build bundle c calendrier camille combal cdi certificate cf client cloudfoundry collaboratif command commandes connexion console css cyanogen decrypt démasquées démasquer développement dll dump easter eggs écologie écrit employeur EMUI EMUI5.0 encrypt enfant évènement export-package ExtJS fab fastboot fiche find firefox gadget galaxytab gelf gem git gmail gnupg gooelappengine google gparted gpg gpg2 gps graylog grenoble Grid gui harddrive heroku hover howto HTML http https IE ihm immobilier imprimante innovation insolite instance integration Java JavaScript jenkins jeu jobs json json-schema-validator key keystore labs linux livre log log4j logger logs lombok masquées masquer maven maven-gae-plugin Mémoire microsoft mobile mockito mondialisation monitor MUSE musique en ligne myopera nodejs npm NT NTEventLogger onglet openstack osgi paas package parameters parent php politique prosyst prototype proxies proxy quartz radio rappel recherche regex repository resize RIA ridge rock ROM route ruby rubygems s8500 samsung scheduler scm secret secure sel selenium Serializer server shared shell sign signature slf4j smartphone so société song spy ssh ssl struct swagger swig tâches téléphone téléréalité test thunderbird timeout token Tomcat tooltip tooltips truststore TWRP ubuntu unit test validator verify virgin virtualbox wave waze web WebApp wiki wikimedia wikipédia wikipen wiko windows windows10 yahoo youtube yum