Java ( 36 articles - Voir la liste )

Erreur java.lang.IllegalStateException

Il arrive de rencontrer des IllegalStateException lorsqu'on effectue des redirections dans des servlets.

En général ça se produit quand on a déjà envoyé des données ou qu'on a déjà demandé une redirection dans notre requête HTTP.

Pour éviter cela, il suffit d'utiliser la méthode isCommited() en remplaçant

// ServletResponse response

if (response != null) {
 response.sendRedirect("http://www.google.fr");
 return;
}

par

// ServletResponse response

if (response != null && !response.isCommited()) {
 response.sendRedirect("http://www.google.fr");
 return;
}

Astuce Impossible de trouver mon nouveau skin dans JCMS

Je viens de créer un nouveau skin dans JCMS, mais malgré un redémarrage ou le vidage du work de tomcat, il n'apparaît pas dans la liste de choix du skin de mes portlets.

Par défaut, pour ajouter un nouveau skin il suffit de créer la jsp et de la déclarer dans le fichier plugin.xml. Par exemple :

<types>
    <templates type="AbstractPortletSkinable">
       <template name="newSkin" file="doNewSkin.jsp" usage="box">
             <label xml:lang="en">My new skin</label>
             <label xml:lang="fr">Mon nouveau skin</label>
       </template>
    </templates>
</type>

Il existe cependant un option méconnue dans le CMS : la possibilité d'activer des skins différents pour chaque espace de travail.

Pour cela :

  • Choisissez un espace de travail en back-offfice
  • Allez dans l'Espace d'administration fonctionnelle > Types de publication > Portlet
  • Éditez [Abstract] Portlet Skinable.
  • Cochez les skins à activer dans l'espace de travail

Par défaut dans JCMS, aucun n'est coché. Tous les nouveaux skins sont donc immédiatement disponibles.

Mais à partir du moment où l'un d'eux est coché, vous devrez repasser par cette interface à chaque création de skin pour l'activer manuellement.

Astuce Autoriser plusieurs soumissions de formulaire

Par mesure de sécurité, JCMS limite le nombre de soumissions des formulaires. Il faut attendre environ une minute entre chaque soumission.

Ce comportement peut-être éviter en ajoutant cette classe css sur la balise form du formulaire : noSingleSubmitButton.

Astuce Initialiser une variable de classe

Lorsqu'on utilise des variables statiques, on peut souvent les initialiser directement à leur déclaration :

public class MaClasse {

    public static String myStaticString = "Ma chaîne";
    public static String[] myStaticArray = {"Ma chaîne 1", "Ma chaîne 2"};
}

Mais si vous avez une Collection ou un autre objet en variable de classe, vous ne pourrez pas l'initialiser de cette manière.

Vous pouvez donc déclarer un bloc statique, qui sera exécuté une seule fois, à la première utilisation de la classe. Dans ce bloc, appelez une fonction statique qui se chargera d'initialiser votre variable :

public class MaClasse {

    public static List<String> myStaticList = new ArrayList<String>();

    static {
        MaClasse.initMyStaticList();
    }

    private static void initMyStaticList() {

        MaClasse.myStaticList.add("Ma chaîne 1");
        MaClasse.myStaticList.add("Ma chaîne 2");
    }
}

Astuce Ajouter un espace (ou autre) tous les n caractères

Si vous souhaitez formater un numéro de téléphone, un IBAN, ou n'importe quelle chaîne en y ajoutant régulièrement un séparateur, cette fonction peut vous être utile :

/**
 * Formate une chaîne en ajoutant un séparateur tous les <code>length</code> caractères.
 * 
 * @param string Chaîne à formater
 * @param separator Séparateur à ajouter
 * @param length Taille des groupes de caractères à séparer
 * @return La chaîne formatée
 */
public static String addChar(String string, String separator, int length) {

    if (string != null && string.length() > length) {

        string = string.replaceAll("([^\\s]{" + length + "})", "$0" + separator);
    }

    return string;
}

Astuce Afficher une erreur dans JCMS

Dans JCMS (7 et +) les erreurs sont affichées dans un bloc ressemblant à ça.

Affichage d'une erreur dans JCMS

Transmettre le message

Vous devez tout d'abord transmettre votre message à JCMS.

  • Si vous êtes dans une JSP, utilisez l'une ou l'autre de ces méthodes :
// Ajoute un message d'erreur dans la request
setErrorMsg("Le téléchargement du document a échoué", request);

// Ajoute un message d'erreur dans la session
setErrorMsgSession("Le téléchargement du document a échoué", request);
  • Pour faire la même chose dans une classe Java, utilisez l'une de celles-ci :
    // Ajoute un message d'erreur dans la request
    JcmsContext.setErrorMsg("An error occured while saving your content", request);

    // Ajoute un message d'erreur dans la session
    JcmsContext.setErrorMsgSession("An error occured while saving your content", request);

Remarque :

Des méthodes équivalentes existent pour les messages d'information et d'avertissement.

Afficher le message

Une fois le message transmis, il reste à l'afficher. Il suffit pour cela d'inclure ce bout de code :

<%@ include file='/jcore/doMessageBox.jsp' %>

Cette JSP fournie par JCMS, va récupérer tous les messages d'information, d'avertissement ou d'erreur présents en session, dans les paramètres de la request et dans le contexte de la page. Chaque message trouvé est affiché.

Remarque :

Depuis la version 7.1 JCMS utilise Bootstrap. Si vous êtes en version antérieure, le message d'erreur ne ressemblera pas à celui ci-dessus.

Astuce Ordre des champs en ExtraData

Lors de l'affichage des champs en ExtraData, JCMS utilise l'ordre l'alphabétique des clés des propriétés d'ExtraData.

Si votre plugin.prop contient les propriétés suivantes :

extra.Category.jcmsplugin.monplugin.premierchamp
extra.Category.jcmsplugin.monplugin.champ2
extra.Category.jcmsplugin.monplugin.champ3
extra.Category.jcmsplugin.monplugin.dernierchamp

Les champs s'afficheront dans cet ordre en back-office :

  • champ2
  • champ3
  • dernierchamp
  • premierchamp

Astuce Tester si une chaîne est vide ou ne contient que des espaces

Voici une méthode pour tester si une chaîne est vide ou contient uniquement des espaces :

/**
 * Retourne si la chaîne en argument est vide ou contient uniquement des espaces.
 * 
 * @param string Chaîne
 * @return <code>true</code> si la chaîne est nulle, vide, ou s'il ne contient que des caractères espaces (ex: \n \r \s, ...) y compris les espaces insécables.
 */
public static boolean isEmpty(String string) {

    boolean isEmpty = true;

    if (string != null) {

        isEmpty = "".equals(string.replaceAll("[\\s|\\u00A0]+", ""));
    }

    return isEmpty;
}

Remarque :

\u00A0 représente les espaces insécables. Si on utilise uniquement \s dans le remplacement, ils ne seront pas considérés comme des espaces.

Marque-page Un décompilateur Java dans Eclipse

L'application Java Decompiler permet de décompiler un fichier .class simple ou tous ceux présents dans un JAR.

Le site de l'application fournit un plugin Eclipse qui ne fonctionne pas avec les dernières versions (testé avec Eclipse Juno). Plusieurs fork ont été créés et ce site en propose un qui fonctionne : JDEclipse-Realign.

Pour l'installer :

  • Cliquez sur Help > Install New Software... dans le menu d'Eclipse
  • Ajoutez un nouveau site via le bouton Add...
  • Choisissez un nom (ex: JDEclipse-Realign) et cette URL : http://mchr3k-eclipse.appspot.com/
  • Cochez JavaDecompiler Eclipse Plug-in > JD-Eclipse (Realign Edition) et suivez la procédure d'installation

Une fois installé et Eclipse redémarré, vérifiez l'association des fichiers :

  • Windows > Preferences > General > Editors > File Associations
  • Pour *.class without source sélectionnez Class File Editor par défaut

Si vous ouvrez un fichier .class (via F3 ou Ctrl + clic sur le nom d'une classe) vous devriez maintenant voir la source décompilée.

Astuce Utiliser la réflexivité

Instancier un nouvel objet d'une classe

/**
 * Retourne une publication du type en paramètre en remplissant automatiquement ses champs, et après l'avoir créer dans JCMS.
 * 
 * @param clazz La classe du type de contenu souhaité
 * @return une publication du type souhaité
 */
public static <T extends Publication> T fillPublication(Class<T> clazz) {

    Publication publication = null;
    if (Util.notEmpty(clazz)) {

        try {
            publication = clazz.newInstance();

        } catch (InstantiationException e) {

            LOGGER.error("Impossible d'instancier dynamiquement un objet de la classe \"" + clazz.getSimpleName() + "\"", e);

        } catch (IllegalAccessException e) {

            LOGGER.error("Impossible d'instancier dynamiquement un objet de la classe \"" + clazz.getSimpleName() + "\"", e);
        }
    }
    return clazz.cast(publication);
}

Appeler une méthode d'une classe

Publication publication = new Article();
Class<? extends Publication> clazz;

Method method = clazz.getMethod("getName");
String name= (String) method.invoke(publication, null);

L'intérêt de ce code est d'appeler une méthode de l'objet publication sans savoir que c'est un article.

Instancier un tableau d'objets d'une classe

int size = 3;
Objet[] publicationArray = (Publication[]) java.lang.reflect.Array.newInstance(Publication.class, size);

Astuce Modifier la locale utilisée dans les JSTL

Dans les JSTL, la locale permet de déterminer comment formater une date ou un nombre. Par défaut, c'est la JVM qui détermine cette valeur.

Pour modifier cette valeur pour toute une session, il suffit d'utiliser :

<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<fmt:setLocale value="fr_FR" scope="session" />

Vous pouvez aussi paramétrer cette valeur pour toute une portlet en ajoutant ces lignes au web.xml, directement sous la racine <web-app> :

<context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
    <param-value>fr_FR</param-value>
</context-param>

Astuce Afficher les retours à la ligne en JSTL

Si vous permettez à des contributeurs de saisir un texte avec des retours à la ligne (= textarea), vous aurez sans doute besoin de les afficher ensuite.

Il suffit de remplacer les retours charriots Java (\n) en retour à la ligne HTML (<br />) :

<c:set var="newline" value="<%= \"\n\" %>" />
${fn:replace(myAddress, newline, "<br />")}

Remarque :

Pour pouvoir utiliser la fonction de remplacement de JSTL, vous devez ajouter cet import :

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

Astuce Chiffrer et déchiffrer une chaîne en Java

Si vous avez besoin de chiffrer une chaîne de caractères en Java, vous pouvez utiliser ces deux méthodes :

/**
 * Chiffre la chaîne en argument en utilisant la clé fournie.
 * Ajoute le suffixe à la chaîne chiffrée puis convertit le tout en base64.
 * 
 * @param string Chaîne à chiffrer
 * @param keyString Clé de chiffrement
 * @param suffix Suffixe à ajouter à la chaîne (ex: la date au format yyyyMMdd)
 * @return la chaîne chiffrée + le suffixe, en base64
 * @throws RESTTechnicalException
 */
public static String encrypt(String string, String keyString, String suffix) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {

    String encryptedB64String = null;

    // Chiffrement de la chaîne
    Key key = new SecretKeySpec(keyString.getBytes("UTF-8"), "Blowfish");
    Cipher cipher = Cipher.getInstance("Blowfish");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encryptedString = cipher.doFinal(string.getBytes("UTF-8"));

    // Ajout du suffixe
    encryptedString = JavaUtil.mergeArray(encryptedString, suffix.getBytes());

    // Encodage de la chaîne en base 64
    encryptedB64String = Base64.encodeBase64String(encryptedString);

    return encryptedB64String;
}

/**
 * Déchiffre la chaîne en argument en utilisant la clé fournie.
 * 
 * @param b64String Chaîne chiffrée + le suffixe, en base 64
 * @param keyString Clé de déchiffrement
 * @param suffix Suffixe à ajouter à la chaîne (ex: la date au format yyyyMMdd)
 * @return la chaîne déchiffrée
 * @throws RESTTechnicalException
 */
public static String decrypt(String b64String, String keyString, String suffix) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {

    String decryptedString = null;

    // Décodage de la chaîne en base 64
    byte[] string = Base64.decodeBase64(b64String);

    // Suppression du suffixe
    string = JavaUtil.truncateArray(string, suffix.length());

    // Déchiffrement de la chaîne
    Key key         = new SecretKeySpec(keyString.getBytes("UTF-8"), "Blowfish");
    Cipher cipher     = Cipher.getInstance("Blowfish");
    cipher.init(Cipher.DECRYPT_MODE, key);
    decryptedString = new String(cipher.doFinal(string));

    return decryptedString;
}

Explications :

  • La chaîne est (dé)chiffrée grâce à la bibliothèque Cipher de Java, en utilisant la technique de chiffrement Blowfish.
  • Le suffixe en argument est ensuite concaténée à la chaîne chiffrée.
  • La chaîne résultante est finalement convertie en base64.

Remarques :

  • La clé de chiffrement ne doit pas excéder 12 caractères, ou des paramètres doivent être changés dans la JVM.
  • Ces méthodes utilisent la bibliothèque org.apache.commons.codec, qui n'est pas forcément disponible dans le JDK.
  • Le suffixe permet de gérer l'expiration du chiffrement. Par exemple, pour une validité de 24h, il suffit de passer la date au format yyyyMMdd en argument.
  • La conversion en base64 n'est pas indispensable mais permet d'obtenir une chaîne de caractères plus simple et exploitable dans une URL par exemple.
  • Le code des méthodes JavaUtil.truncateArray() et JavaUtil.mergeArray() est disponible ci-dessous :

    /**
     * Tronque le tableau de byte en arguments.
     * 
     * @param array Tableau à tronquer
     * @param maxSize Taille maximale du tableau à conserver
     * @return le tableau tronqué
     */
    public static byte[] truncateArray(byte[] array, int maxSize) {
    
        int newArraySize = Math.min(array.length, maxSize);
        byte[] newArray = new byte[newArraySize];
        System.arraycopy(array, 0, newArray, 0, maxSize);
    
        return newArray;
    }
    
    /**
     * Merge les deux tableaux en arguments.
     * 
     * @param first Premier tableau
     * @param second Second tableau
     * @return
     */
    public static byte[] mergeArray(byte[] first, byte[] second) {
    
        byte[] result = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, result, first.length, second.length);
    
        return result;
    }

Astuce Rétablir le mot de passe admin par défaut

Si vous avez perdu le mot passe administrateur, ou si vous souhaitez le modifier directement dans le store, ajoutez une ligne de la forme suivante à la fin :

<member stamp="j_99999" id="j_2" op="update" author="" mdate="1091526019085" mobile="" name="Admin" password="ISMvKXpXpadDiUoOSoAfww==" />

Cette ligne remplace le mot de passe actuel par admin.

Remarques :

  • Remplacez la valeur de stamp par la même valeur que votre dernière ligne de store, en l'incrémentant.
  • Faites de même pour la date de modification (mdate).

JPlatform 10 (merci Axel pour cette MAJ)

Sur JPlatform 10, le chiffrement des mots de passe a changé. La ligne à ajouter pour le réinitialiser devient :

<member stamp="agi_7217" id="j_2" op="update" mdate="1528122795429" login="admin" name="Admin" password="$2a$10$gbCw7UaAI1kL7PXqodTY9OmzqsdFBufD063oG6ebQT/Zi2PBELX56" />

Erreur Impossible de mapper des sous-classes avec JSON

Si vous utilisez un mapping JSON en Java, vous pouvez rencontrer l'erreur suivante :

org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type

Cette erreur se produit si vous utilisez un Bean global contenant une ou plusieurs classes internes. Exemple :

public class MyGlobalClass {

    private List<SubClass> subClassList;

    public class SubClass {

        private String id;
    }
}

Pour ne pas avoir cette erreur, déclarez vos classes internes statiques dans la classe globale :

public static class SubClass {

    private String id;
}

Astuce Afficher la valeur de tous les champs via la méthode toString()

Lorsque vous créez une nouvelle classe en Java, elle hérite de la méthode toString() de la classe Object. Par défaut, cette méthode n'affiche rien de bien compréhensible.

Vous pouvez la surcharger pour afficher lisiblement les valeurs de tous les champs de votre objet de cette manière :

@Override
public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}

Grâce à l'introspection et à la librairie org.apache.commons, ces quelques lignes de code fonctionneront pour toutes vos classes.

Astuce Le logger dans JCMS : log4j

Par défaut, JCMS utilise log4j pour gestionnaire de logs.

Utilisation générale de log4j

Voici comment l'utiliser dans une classe :

public class MyClass {

    /** Logger log4j */
    private static final Logger logger = Logger.getLogger(MyClass.class);

    public MyClass() {
        logger.info("Je suis dans le constructeur par défaut de ma classe.");
    }

Explications :

  • Déclarez un nouvel attribut de classe statique qui utilise la méthode getLogger() de log4j.
  • Passez votre classe en argument de cette méthode.
  • Utilisez le logger en appelant ses méthodes debug(), info(), warn(), error() ou fatal(), avec le message en argument, plus éventuellement une exception.

Configuration de log4j

Log4j est configuré via le fichier log4j.xml. Dans JCMS, ce fichier se trouve dans WEB-INF/data/.

Si dans un nouveau projet JCMS vous créez un package pour y mettre vos classes Java, il est probable que les logs ne fonctionnent pas. En effet, log4j n'a pas connaissance de ce nouveau package.

Pour l'en informer, éditez le fichier log4j.xml et ajoutez :

<!-- Logger de classes personnalisées --> 
<logger name="mon.package" additivity="false">
    <level value="DEBUG" />
    <appender-ref ref="JCMS" />
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="LOGFILE" />
    <appender-ref ref="PLUGIN" />
</logger>

Explications :

  • L'attribut name définit pour quels packages utiliser log4j. (Les sous-packages seront automatiquement logués.)
  • L'attribut value de l'élément <level> permet de déterminer quel niveau de log appliquer. En développement on utilise souvent DEBUG ou INFO alors qu'en production on se contentera de INFO ou WARN. Concrètement, avec le niveau WARN, les méthodes logger.debug() et logger.info() n'auront aucun effet (suivant cet ordre : debug < info < warn < error < fatal).
  • Les <appender> sont les sorties à utiliser : fichier de log, console, ...
  • Vous pouvez déclarer autant de logger que vous le souhaitez dans le fichier log4j.xml.

Astuce Récupérer la version antérieure d'une Data, dans un DataController

Si vous avez besoin de comparer l'ancienne et la nouvelle version d'une Data, dans un DataController (par exemple dans afterWrite()), vous aurez besoin d'utiliser le contexte en paramètre de la méthode.

Pour obtenir cette version, utilisez quelque chose comme :

final Article oldArticle = (Article) context.get("Data.previous");

Astuce Les variables disponibles dans le contexte, pour les DataController

Lorsque vous créez un DataController et que vous surchargez les méthodes beforeWrite(), checkWrite() et afterWrite(), le dernier paramètre de la méthode (la Map), contient tout le contexte disponible.

Dans cette variable il y a notamment :

  • request : L'objet HttpServletRequest de tomcat.
  • response : L'objet HttpServletResponse de tomcat.
  • formHandler : Le FormHandler utilisé lors de l'édition du contenu.

Et dans le cas d'une modification de donnée, il y a également :

  • Data.previous : La dernière version de la Data avant modification.

Astuce Utiliser un logger dans les classes de test

Dans vos classes de test, vous pouvez utiliser le gestionnaire de log slf4j Il s'agit d'une façade qui peut utiliser plusieurs loggers au choix, comme log4j par exemple.

Voici comment utiliser ce gestionnaire :

public class MyClassTest extends JcmsTestCase {

    /** Logger **/
    private static Logger logger;

    /**
     * Initialise un contexte pour une suite de tests, avec des méthodes d'initialisation et de finalisation.
     * 
     * @return une configuration de suite de tests
     */
    public static TestSetup suite() {

        return new TestSetup(new TestSuite(MyClassTest.class)) {

            @BeforeClass
            @Override
            protected void setUp() throws Exception {

                logger = LoggerFactory.getLogger(MyClassTest.class);
                logger.debug("Début des tests unitaires pour la classe " + MyClass.class.getSimpleName() + ".");
            }
            @AfterClass
            @Override
            protected void tearDown() throws Exception {
                logger.debug("Fin des tests unitaires.");
            }
        };
    }

Explications :

  • Déclarez le logger dans votre classe de test.
  • Créez la méthode statique suite(), pour pouvoir initialiser des éléments au début des tests.
  • Utilisez la méthode LoggerFactory.getLogger(), pour initialiser le logger.
  • Utilisez le logger comme avec log4j (ex : logger.info(), logger.debug(), ...).

Remarque :

Pour pouvoir utiliser slf4j avec log4j, vous devez inclure ces deux jars dans votre projet : slf4j-api-1.5.8.jar et slf4j-log4j12-1.5.8.jar.

Astuce Vider toutes les tables de la base de données Derby

Si vous utilisez Derby dans vos tests unitaires, vous aurez sans doute besoin de réinitialiser votre base entre chaque test.

Pour supprimer le contenu de toutes les tables d'un coup, vous pouvez utiliser cette fonction :

/**
 * Vide toutes les tables de la base de données, dont le nom commence par "G_" ou "J_".
 * 
 * @throws SQLException
 */
public static void derbyClearAllTables() throws SQLException {

    ResultSet tables;
    Statement stat;

    final String schema = "jdbc:derby:jcmsdbunit";

    // Récupération de la liste des tables de la base de données
    final Connection connection = DriverManager.getConnection(schema);
    tables = connection.getMetaData().getTables(null, null, null, null);

    final Set<String> tableNameSet = new HashSet<String>();

    // Parcours des tables de la base de données
    while (tables.next()) {

        final String tableName = tables.getString("TABLE_NAME");

        if (tableName.startsWith("G_") || tableName.startsWith("J_")) {

            tableNameSet.add(tableName);
        }
    }

    // Parcours des tables à vider
    for (final String tableName : tableNameSet) {

        // Nettoyage de la table
        stat = connection.createStatement();
        stat.executeUpdate("DELETE FROM " + tableName);
        connection.commit();
    }

    if (!tables.isClosed()) {
        tables.close();
    }
}

Remarque :

Les tables ne commençant ni par G_ ni par J_ sont probablement des tables utiles au fonctionnement interne de Derby. Elles ne sont pas à vider.

Astuce Méthode d'initialisation au début de la classe de test

Pour effectuer un traitement une fois au début des tests, on utilise généralement la méthode suivante dans la classe de test :

@BeforeClass
public static void oneTimeSetUp() {

    logger = LoggerFactory.getLogger(MyClassTest.class);
    logger.info("Début des tests unitaires.");
}

Cela permet d'initialiser un logger, configurer l'environnement à utiliser pour tous les tests, ...

Cette méthode ne semble pas fonctionner avec JCMS (dans eclipse en tous cas). Vous pouvez faire l'équivalent grâce à la méthode suite() à ajouter dans votre classe de test :

/**
 * Initialise un contexte pour une suite de tests, avec des méthodes d'initialisation et de finalisation.
 * 
 * @return une configuration de suite de tests
 */
public static TestSetup suite() {

    return new TestSetup(new TestSuite(MyClassTest.class)) {

        @Before
        @Override
        protected void setUp() throws Exception {
            logger = LoggerFactory.getLogger(MyClassTest.class);
            logger.info("Début des tests unitaires.");
        }

        @After
        @Override
        protected void tearDown() throws Exception {
            logger.info("Fin des tests unitaires.");
        }
    };
}

Remarque :

La méthode tearDown() permet d'effectuer un traitement une fois après tous les tests.

Astuce Utiliser les Singletons en Java avec du multithreading

Dans un environnement multithread, le comportement d'un singleton peut être faussé.

Pour éviter ce problème, il suffit d'ajouter le qualificatif synchronized à la méthode du singleton :

public static synchronized Singleton getInstance() { 

    if (instance == null) {
        instance = new Singleton();
    }

    return instance;
}

Astuce Le tag @see de la Javadoc

Lorsque vous écrivez la Javadoc d'une méthode, vous pouvez ajouter un lien vers une autre méthode, grâce au tag @see.

Pour cela vous devez respecter cette syntaxe :

/**
 * Méthode d'exemple.
 * @see {@link mypackage.MyClass#someMethod}
 */
public void myMethod() {

}

ou

/**
 * Méthode d'exemple.
 * @see {@link mypackage.MyClass#someMethod(Arg1Class, String)}
 */
public void myMethod() {

}

/**
 * Méthode utilisée par myMethod.
 */
public void someMethod(Arg1Class arg1, String arg2) {

}

Remarque :

Pour ajouter plusieurs liens, séparez-les simplement par une virgule.

Erreur Erreur au démarrage de JCMS : lucene

Si votre lanceur de projet eclipse est mal configuré, vous pouvez obtenir l'erreur suivante au démarrage de JCMS, pendant l'initialisation des dépendances des plugins :

09:59:34,749 FATAL [JCMS] [ChannelInitServlet] - An exception occured while initializing JCMS. The site is not available.
java.lang.NoClassDefFoundError: org/apache/lucene/analysis/standard/StandardAnalyzer
    at java.lang.ClassLoader.defineClass1(Native Method)
    ...

Pour éviter cette erreur, vérifiez l'onglet Classpath de votre lanceur eclipse et supprimez votre projet des User Entries :

User Entries

Astuce La méthode getStringArrayProperty() de la classe Channel

Dans JCMS, la méthode getStringArrayProperty() de la classe Channel permet de récupérer un tableau de valeurs définies dans une même propriété.

Par exemple :

Channel.getChannel.getStringArrayProperty("jcmsplugin.myplugin.name-list", new String[] {})

avec dans un plugin.prop la propriété suivante :

jcmsplugin.myplugin.name-list: Jean Louis David Roger,Virgule

retournera un tableau (String[]) de quatre éléments : Jean, Louis, David et Roger,Virgule.

Remarques :

  • Le séparateur par défaut est l'espace (ou tabulation et autres chaînes d'espace)
  • Pour modifier le séparateur, il faut l'indiquer au début de la liste de valeurs, avec un @.
jcmsplugin.myplugin.name-list: @,Jean Louis David Roger,Virgule

La méthode appelée précédemment retournera cette fois un tableau de deux valeurs : Jean Louis David et Virgule.

Astuce Ouvrir un jalios:link dans une nouvelle fenêtre

JCMS fournit un tag pour créer des liens : <jalios:link>.

Pour que ce lien ouvre la cible dans une nouvelle fenêtre, il faut utiliser l'attribut htmlAttributes du tag. Par exemple :

<jalios:link data='<%= doc %>' htmlAttributes='target="_blank"' title='<%= glp("jcmsplugin.myplugin.target-blank.title") %>'>
    <%= doc.getTitle(userLang) %>
</jalios:link>

Remarque :

Pour l’accessibilité, il est conseillé de préciser l'attribut title pour informer l'utilisateur de l'ouverture dans une nouvelle fenêtre.

Astuce Désactiver un module JCMS

Pour empêcher un module d'être chargé au démarrage de JCMS, modifiez son plugin.xml.

Passez l'attribut initialize de l'élément racine à false.

Astuce Simplifier l'utilisation des id dans les fichier de propriétés

JCMS permet de simplifier la récupération d'une Data à partir de son id, stocké dans un fichier de propriétés.

Par exemple, pour stocker l'id d'une catégorie et la récupérer, vous feriez

jcmsplugin.myplugin.category.my-category.id: j_6540

et

String categoryID = channel.getProperty("jcmsplugin.myplugin.category.my-category.id");
Category category = channel.getCategory(categoryID);

Utilisez

$jcmsplugin.myplugin.category.my-category.id

et

Category category = channel.getCategory("$jcmsplugin.myplugin.category.my-category.id");

Explication :

Comme son nom commence par $, JCMS sait qu'il s'agit d'une propriété contenant un id, et peut directement récupérer la Data associée.

Astuce Utiliser l'ajax-refresh pour recharger un élément du DOM

JCMS intègre un framework pour utiliser de l'AJAX dans les pages d'un site.

Pour recharger un élément du DOM à partir d'une jsp, voici le code à utiliser :

var id          = 'mon_element';
var url         = 'plugins/MonPlugin/jsp/ajax/maJsp.jsp';
var value       = $(id).value;
var params      = 'key=' + value;

// Rechargement de l'élément
JCMS.ajax.Refresh._refresh(id, url, {
    history: false,
    formParams: params
});
  • L'id est celui de l'élément dont le contenu doit être rechargé (innerHTML).
  • L'url est le chemin vers la jsp (et pas jspf) qui génère le contenu.
  • params est la liste des paramètres à ajouter à l'url, qui pourront être récupérés dans la jsp appelée :
String keyValue = request.getParameter("key");

Astuce Ne pas compiler les .less à chaque démarrage

Si vous n'utilisez pas les fichiers .less dans vos modules et que vous ne modifiez pas ceux de JCMS, il est inutile de les recompiler à chaque redémarrage, car les fichiers .css générés ne changeront pas.

Pour éviter cette compilation automatique, ajoutez cette propriété au custom.prop :

channel.less-compile.startup: false

Erreur javac/Main introuvable au lancement de la webapp

Lorsque vous lancer une application JCMS avec tomcat 1.5, vous pouvez avoir cette erreur :

java.lang.NoClassDefFoundError: com/sun/tools/javac/Main

Elle signifie qu'il manque l'archive tools.jar au classpath.

Pour l'ajouter à votre projet au lancement de l'application, effectuez ces quelques étapes :

  • Clic-droit sur le projet
  • Run As > Run Configurations...
  • Onglet Classpath
  • Cliquez sur User Entries puis sur le bouton Advanced...
  • Sélectionnez Add Variable String:
  • Copiez-collez le chemin vers le fichier tools.jar qui se trouve dans le répertoire lib/ de votre jdk (ex: D:\Dev\DevPack\jdk\lib\tools.jar)

Astuce Choisir le portail d'affichage d'une publication

Lorsque vous affichez une publication en Full display, le portail d'affichage choisi sera celui de la première catégorie à laquelle est est rattachée. Si cette catégorie n'a pas de portail qui lui est propre, celui de sa catégorie parente est utilisée, et ainsi de suite jusqu'à trouver un portail d'affichage.

Si le contenu n'est pas catégorisé, le portail d'affichage par défaut sera utilisé.

Pour forcer une publication à s'afficher dans un portail spécifique, on peut ajouter un paramètre à l'url : &portal=<portal_id>.

Avec un tag jalios:link on obtient donc quelque chose comme :

<jalios:link data='<%= publication%>' paramNames='<%= paramNames %>' paramValues='<%= paramValues %>'>

Explication :

  • paramNames : Tableau de String contenant les paramètres à ajouter à l'url (ici: {"portal"})
  • paramValues : Valeurs des paramètres du premier tableau, dans l'ordre correspondant (ici: {channel.getProperty("jcmsplugin.portail.full-display.id")})

Remarque :

JCMS 7.1 comporte un bug à propos de ce tag, et les paramètres paramsNames et paramValues ne fonctionnent pas. On doit alors utiliser le paramètre params :

<jalios:link data='<%= publication %>' params='<%= "portal=" + channel.getProperty("jcmsplugin.portail.full-display.detail.id") %>'>

Ce paramètre reçoit une valeur de la forme <paramName>=<paramValue>. Il remplace tous les autres paramètres transmis initialement dans l'url et est donc à éviter lorsque paraNames et paramValues fonctionnent.

Astuce Démarrer un nouveau projet avec JCMS

Cet article explique comment démarrer un nouveau projet JCMS 7.X avec un développement en modules synchronisés.

Environnement

  • Commencez par récupérer et déployer un devpack JCMS. ()Par exemple, extraire le devpack dans D:\Dev).
  • Lancez eclipse via le lanceur eclipse.bat.

Création du projet

  • Créez un nouveau projet Java avec la configuration par défaut.
  • Récupérez et extrayez une webapp JCMS 7.X vierge à la racine de ce nouveau projet.
  • Clic-droit > Refresh sur le projet.

Configuration du projet

Il faut préciser à eclipse quelles classes compiler et avec quelles bibliothèques.

  • Clic-droit > Properties sur le projet
  • Java Build Path > Source :
    • Supprimez le dossier src/ existant, et ajoutez le dossier WEB-INF/classes/.
    • Choisissez ce même dossier pour qu'y soient générées les .class (default output folder).
  • Java Build Path > Libraries > Add Library... > User Libraries... :
    • Créez une bibliothèque Tomcat6 (elle sera réutilisable pour tous vos futurs projets) :
      • Cliquez sur New..., nommez votre bibliothèque Tomcat6 et validez.
      • Cliquez sur Add JARs... et parcourez l'arborescence jusqu'au dossier tomcat6/lib/ de votre Devpack.
      • Sélectionnez les fichiers ecj-3.7.jar, el-api.jar, jasper.jar, jasper-el.jar, jsp-api.jar, servlet-api.jar et validez.
    • De la même manière créez la bibliothèque MonProjet, en sélectionnant cette fois tous les fichiers présents dans le dossier WEB-INF/lib/ de JCMS.
    • Cochez maintenant ces deux bibliothèques et validez, pour qu'elles soient ajoutées à votre projet.
  • Validez les propriétés de votre projet.

Lancement de l'application JCMS

Pour pouvoir lancer l'application, vous devez indiquer le paramétrage de tomcat et les configurations d'exécution d'eclipse.

  • Copiez le fichier MonProjet.launch à la racine de votre projet.
  • Dans ce fichier, remplacez MonProjet par le nom de votre projet, et D:/Dev/DevPack/ par le chemin vers votre devpack.
  • Si votre workspace ou les dossiers jdk16/ et tomcat6/ ne se trouvent pas à la racine de votre devpack, modifiez les chemins correspondants en conséquence.
  • Éditez le fichier tomcat.xml à la racine de votre projet en adaptant au besoin le chemin vers la racine de votre application et le contexte (/jcms par défaut). Exemple :
<Context path="/jcms" docBase="D:/Dev/workspaces/MonProjet" debug="0"></Context>
  • Clic-droit > Refresh sur le projet
  • Cliquez sur la petite flèche près du bouton Démarrer JCMS et cliquez sur MonProjet.
  • Accédez au site à l'url http://localhost:8080/jcms, (/jcms étant le contexte choisi dans le tomcat.xml).

Astuce Modifier le JDK ou le JRE sous Windows

Lorsque vous installez un JDK ou un JRE, il est probable qu'il y ait déjà un autre JRE présent.

Pour que Windows utilise le nouveau, vous devez modifier une variable d'environnement :

  • appuyez sur Windows + Pause
  • Paramètres système avancés
  • Bouton Variables d'environnement
  • Sélectionner la variable système à modifier : Path.

Vous devez ajouter au tout début le chemin vers le dossier bin présent dans le dossier d'installation de votre nouveau JRE ou JDK. Ex:

C:\Program Files (x86)\Java\jre1.8.0_31\bin

Sous Windows 10, vous avez une liste de chemin, il suffit d'en ajouter un et de le déplacer tout en haut. Pour les versions antérieures de Windows, tous les chemins sous mis bout à bout, et séparés par une virgule. Par exemple, après avoir installé le JRE 1.8 :

Modifier le JDK ou le JRE sous Windows

Validez ensuite avec ok pour chaque fenêtre.

Vérifications

Pour vérifier que votre variable est bien prise en compte, ouvrez une invite de commande et tapez :

echo %Path%

Pour vérifier la version de java et javac courante, tapez les commandes :

java -version
javac -version