Archives pour la catégorie Developpement

MongoDB quick query memo

MONGO SHELL

Connects to the FOO DB :

> use FOO

Connects to current DB (FOO) in the users collection and find out all items :

> db.users.find()

Connects to current DB (FOO) in the users collection and find out all items and print out in a pretty way :

> db.users.find().pretty()

Find the first item :

> db.users.findOne()

Find all items matching the query :

> db.users.find(<QUERY>)

Next page of the result iteration :

> it

Shows all the available databases :

> show databases

Set the profiling level (level (0: Off, 1:Slow queries, 2: All) ms for slow queries) :

> db.setProfilingLevel(1,4)
> db.getProfilingLevel()
> db.getProfilingStatus()

Shows the system logs for queries with « users.name » sorted by timestamp :

> db.system.profile.find({ns:/users.name/}).sort({ts:1})

Realtime MongoDB statistics in command line (Linux/Mac) :

$ mongostat

Realtime MongoDB top command line (Linux/Mac). Gets wich collections are used in the moment (Param : number of seconds of refresh). The results shows the time spend in each collection in the interval of the param :

$ mongotop 3

MONGO QUERIES

Print out all items where name matches with « Stefan » :

> db.users.find({name:"Stefan"})

Print out all items where name matches with « Stefan » and age with 43 :

> db.users.find({name:"Stefan", age:43})

Print out all items where name matches with « Stefan » and age less than 25 :

> db.users.find({name:"Stefan", age:{$lt:25}})

Print out all items where name matches with « Stefan » and age greater or equal to 35 :

> db.users.find({name:"Stefan", age:{$gte:35}})

All items where name= »Stefan » and having an « age » field :

> db.users.find({name:"Stefan", age:{$exists:true}})

All items having « String » type « name » field (See Bson spec) :

> db.users.find({name:{$type:2}})

All items having « name » field ending with « a » :

> db.users.find({name:{$regex:"a$"}})

All items having « Red » in the « favorite_color » array :

> db.users.find({favorite_color:"Red"})

All items having name matching « Stefan » or « Nils » :

> db.users.find({$or:[{name:"Stefan"},{name:"Nils"}]})

All items having name matching « Stefan » or with age over 40 :

> db.users.find({$and:[{name:"Stefan"},{age:{$gt:40}}]})

All items having name matching « Stefan » and with age over 40 :

> db.users.find({$elemMatch:[{name:"Stefan"},{age:{$gt:40}}]})

All items having « Stefan » and « Nina » in friends array :

> db.users.find({friends:{$all:["Stefan","Nina"]}})

All items having « Stefan » or « Nina » in friends array :

> db.users.find({friends:{$in:["Stefan","Nina"]}})

All items which name are « John » having friends aged over 18 :

> db.users.find({name:"John", friends.age:{$gt:18}})

Counts the number of items having name= »Stefan » :

> db.users.count({name:"Stefan"})

Update items having name= »Stefan » with values name= »Thomas » and age=52 (All other attributes will be lost) :

> db.users.update({name:"Stefan"},{name:"Thomas", age:52})

Update items having name= »Stefan » and change age value to 52. All the other attributes will stay unchanged :

> db.users.update({name:"Stefan"},{$set:{age:52}})

Update items having name= »Stefan » and change age value to 52. If there is no « Kevin » record, a new one will be created :

> db.users.update({name:"Kevin"},{$set:{age:52}}, {upsert:true})

Update the first item found that matches and change its age value to 52. All the other attributes will stay unchanged :

> db.users.update({},{$set:{age:52}})

Update all items and change age value to 52. All the other attributes will stay unchanged :

> db.users.update({},{$set:{age:52}}, {multi:true})

Update items having name= »Stefan » and increase the age value from 1 (52 -> 53) :

> db.users.update({name:"Stefan"},{$inc:{age:1}})

Update items having name= »Stefan » and remove the « age » attribute (Any value will work, not just 1) :

> db.users.update({name:"Stefan"},{$unset:{age:1}})

Modifies the 3rd element of « favorite_colors » collection for « Green » value :

> db.users.update({name:"Stefan"},{$set:{favorite_colors.2:"Green"}})

Add the « Pink » value to the favorite_color array :

> db.users.update({name:"Stefan"},{$push:{favorite_colors:"Pink"}})

Remove value from the right side of the array :

> db.users.update({name:"Stefan"},{$pop:{favorite_colors:1}})

Remove value from the left side of the array :

> db.users.update({name:"Stefan"},{$pop:{favorite_colors:-1}})

Add the elements to the favorite_color array :

> db.users.update({name:"Stefan"},{$pushAll:{favorite_colors:["Pink","Orange","Brown"]}})

Remove the Blue and Red values from the array :

> db.users.update({name:"Stefan"},{$pullAll:{favorite_colors:["Blue","Red"]}})

Add a « Green » element in favorite_color array if it doesn »t exists other wise nothing happends :

> db.users.update({name:"Stefan"},{$addToSet:{favorite_colors:"Green"}})

Remove all items of the collection (Indexes will not be deleted) :

> db.users.remove({})

Remove all items of the collection (Truncate in SQL) :

> db.users.drop()

Remove all items of the collection matching the query (Delete in SQL) :

> db.users.remove({name:"Stefan"})

Remove just one item of the collection matching the query (Delete in SQL) :

> db.users.remove({name:"Stefan"}, {justeOne:true})

Cursors

Assign a result set to a variable named « cur » :

> cur = db.users.find()

Assign a result set to a variable named « cur » without printing result :

> cur = db.users.find(); null;

True if there are still elements :

> cur.hasNext()

Gives the next element :

> cur.next()

Print all the elements of the cur cursor :

> while(cur.hasNext()) printjson(cur.next());

Set a limit of 5 elements :

> cur.limit(5)

Sort in reverse order :

> cur.sort({name : -1})

Limits to 5 results and skip 2 visited items (Begins after 2nd item. Can be used for pagination) :

> cur.limit(5).skip(2)

Indexes

Create an index on « name » ascending and « hair color » descending (single keys but also works with multipleKeys (Arrays)) :

> db.users.createIndex({name:1, hair_color:-1})

Create an index on subdocument value :

> db.users.createIndex({"friends.name":1})

Create a unique index on value :

> db.users.createIndex({name:1}, {unique:true})

Create a unique index on value only for items having all the attributes name and phone (others will not be indexed) :

> db.users.createIndex({name:1, phone:1}, {unique:true, sparse:true})

Create an index in the background. Users (write/read) will not be blocked. Creating this indexes is longer than in foreground (default) :

> db.users.createIndex({"friends.name":1}, {background:true})

Drop an index on « name » ascending and « hair color » descending :

> db.users.dropIndex({name:1, hair_color:-1})

View all indexes of a specific collection :

> db.users.getIndexes()

Shows execution plan to see if there is an index used to execute this query :

> db.users.explain().find({name:'Stefan'})

Shows execution plan with execution stats (Nb of documents, time…) to see if there is an index used to execute this query :

> db.users.explain({"executionStats"}).find({name:'Stefan'})

Shows execution plan of all possibilities (Nb of documents, time…) to see if there is an index used to execute this query :

> db.users.explain({"allPlansExecution"}).find({name:'Stefan'})

Shows the collection statistics (number of indexes …) :

> db.users.stats()

Shows the total index size of a collection (part of statistics) :

> db.users.totalIndexSize()

Geospatial indexes (2D indexes : in this case we use a « location » attribute of x,y values) :

> db.users.createIndex("location":"2D",type:1)
> db.users.find({location:{"$near":[74,140]}}).limit(3);

Geospatial spherical (GeoJson -> 2D indexes over a sphere : in this case we use a « location » attribute of latitude and longitude values) :

> db.users.createIndex("location":"2Dsphere",type:1)
> db.users.find({"location":{"$near":{"$geometry":{type: Point, Coordinates: [-63.12, 98.45]}, "$maxDistance": 2000}}}).limit(3);

Full text indexes, once created you can make queries findings items having a certain word in it (By default MDB uses an ‘or’ keyword between the strings in the query. TextSScore is used to sort the responses by pertinance of the order in the query. It is used to find the best match) :

> db.users.createIndex("myTextAttribute":"Text")
> db.users.find({"$text":{$search : "home dog time"}}, {score : {$meta : "textScore"}}).sort(score: {$meta: "textScore"});

Find with a specefic index (Here we’re looking for users in certain groups. We sort by user_id ascending but force tu use the group_id index in memory and not on disk to optimize) :

> db.users.find({user_id:{$gt:500000}, group_id:54}).sort({user_id:1}).hint({group_id:1})

Aggregation Framework

Group by category and sum items

> db.products.aggregate([{$group:{_id:"$category", num_products:{$sum:1}}}])

Group by « c » attribute

> db.stuff.aggregate([{$group:{_id:'$c'}}])

Group by and create new _id

> db.stuff.aggregate([{$group: {_id: {'moe':'$a', 'larry':'$b', 'curly':'$c' } } }])

Group by state and sum population

> db.zips.aggregate([{$group:{_id:"$state",population:{$sum:"$pop"}}}])

Group by state and get average population

> db.zips.aggregate([{$group:{_id:"$state",population:{$avg:"$pop"}}}])

Group by state and make array of postal codes (Lines to array)

> db.zips.aggregate([{$group:{_id:"$city", postal_codes:{$addToSet:"$_id"}}}]);

Group by and make array : 2 different ways

> db.zips.aggregate([{"$group":{"_id":"$city", "postal_codes":{"$push":"$_id"}}}])
> db.zips.aggregate([{"$group":{"_id":"$city", "postal_codes":{"$addToSet":"$_id"}}}])

Group by state and get max population

> db.zips.aggregate([{$group:{_id:"$state", pop:{$max:"$pop"}}}]);

Double grouping (Group of grouped elements with $min and $max values)

> db.fun.aggregate([{$group:{_id:{a:"$a", b:"$b"}, c:{$max:"$c"}}}, {$group:{_id:"$_id.a", c:{$min:"$c"}}}])

Project attributes in other attributes (Change name to lowerCase for example)

> db.zips.aggregate([{$project:{_id:0, city:{$toLower:"$city"}, pop:"$pop", state:"$state", zip:"$_id"}}])

Find elements that matches population greeater than 10000

> db.zips.aggregate([{$match:{pop:{$gt:10000}}}])

Sorting by state and then city

> db.zips.aggregate([{$sort:{state:1, city:1}}])

Aggregation pipline with $limit and $skip

> db.zips.aggregate([
    {$match: { state:"NY" } },
    {$group: { _id: "$city", population: {$sum:"$pop"}, } },
    {$project: { _id: 0, city: "$_id", population: 1, } },
    {$sort: { population:-1 } },
    {$limit: 5},
    {$skip: 10} 
])

Find first element in reverse order

> db.fun.aggregate([
    {$match:{a:0}},
    {$sort:{c:-1}}, 
    {$group:{_id:"$a", c:{$first:"$c"}}}
])

Array to lines (Array of tags will give one line for each element. New element will be a tags attribute)

> db.zips.aggregate([{$group:{$unwind:"$tags"}} ]);

Double $unwind if multiple arrays

> db.inventory.aggregate([
    {$unwind: "$sizes"},
    {$unwind: "$colors"},
    {$group: { '_id': "$name", 'sizes': {$addToSet: "$sizes"}, 'colors': {$addToSet: "$colors"} } } ])

Select count(*)

> db.zips.aggregate([{$group:{_id:null, count: {$sum:1} } } ]);

Select sum(price)

> db.zips.aggregate([{$group:{_id:null, total: {$sum:"$price"} } } ]);

Get substring of an attribute

> db.zips.aggregate([
    {$project: 
     {
	first_char: {$substr : ["$city",0,1]},
     }	 
   }
])

Replica Set

Create Replica Set

#!/usr/bin/env bash

mkdir -p /data/rs1 /data/rs2 /data/rs3
mongod --replSet m101 --logpath "1.log" --dbpath /data/rs1 --port 27017 --oplogSize 64 --fork --smallfiles
mongod --replSet m101 --logpath "2.log" --dbpath /data/rs2 --port 27018 --oplogSize 64 --smallfiles --fork
mongod --replSet m101 --logpath "3.log" --dbpath /data/rs3 --port 27019 --oplogSize 64 --smallfiles --fork

In Mongo configure Replica Set

> config = { _id: "m101", members:[
          { _id : 0, host : "localhost:27017"},
          { _id : 1, host : "localhost:27018"},
          { _id : 2, host : "localhost:27019"} ]
};

> rs.initiate(config);
> rs.status();

Find and kill Oracle Session by ID

If you want to kill a Oracle session you have to find out the session SID and Serial# :


SELECT s.inst_id,
s.sid,
s.serial#,
s.username,
s.osuser,
s.program,
s.status,
round(s.last_call_et/60) minutes,
nvl(s.sql_id, s.prev_sql_id),
(select q.sql_text from GV$SQLAREA q where q.inst_id = s.inst_id and q.sql_id = nvl(s.sql_id, s.prev_sql_id))
FROM GV$SESSION s
WHERE nvl(s.username,'SYS') = 'PANA'
AND s.LAST_CALL_ET > 60
ORDER BY last_call_et desc
;

 

INST_ID SID SERIAL# USERNAME OSUSER PROGRAM STATUS MINUTES SQL_ID SQL_Query
1 77 12717 ORA_USER SYS_USER JDBC Thin Client INACTIVE 3 9c8tu7j63jraa SELECT * FROM MY_TABLE

After that, you can use the ALTER SYSTEM command to kill the selected session :


ALTER SYSTEM KILL SESSION 'sid, serial#';

Pattern « Visiteur » et généricité de méthodes

Petit exemple d’implémentation de filtrage et transformation de collection dans lequel on implémente le patterne « Visiteur ».

L’intérêt est de créer un code qui est réutilisable, facile a maintenir et générique.

Dans l’exemple suivant on va implémenter une classe « CollectionUtils » avec son interface « ICollectionUtils ». Son rôle va être celui d’un service qui met a disposition des « méthodes génériques » pour effectuer des opérations sur des listes quelque soit le type des paramètres d’entrée et des paramètres de sortie. L’intérêt de rendre les méthodes génériques et non pas classe réside dans le fait qu’on utilisera la même instance d’objet pour traiter des collections de type différents. On aurait aussi pu faire des « classes génériques », mais ça aurait impliqué de créer une instance par type d’objets à traiter.

Notre service va donc proposer 3 méthodes :
filter : prend en paramètre une liste générique et un IEntryFilter qui sera notre implémentation de filtre qui s’appliquera sur chaque élément de la liste.
listToMap : prend en paramètre une liste générique et un IEntryMapper qui sera notre implémentation de mapper qui transformera nos éléments en un objet de type clé / valeur.
transform : prend en paramètre une liste générique et un IEntryTransformer qui sera notre implémentation pour transformer un élément d’un type en un autre.

Voici l’implémentation de notre service et de sont interface :

import java.util.List;
import java.util.Map;

/**
 * Filtrage et transformation des noeuds JCR
 *
 */
public interface ICollectionUtilsService {
    /**
     * Methode de filtrage des noeuds d'une liste
     * @param nodeList List<Node>
     * @param filter List<Node>
     * @return List<Node>
     */
    public <U> List<U> filter(List<U> nodeList, IEntryFilter<U> filter);

    /**
     * Methode de transformation d'une List en Map
     * @param nodeList List<Node>
     * @return <T>
     * @throws Exception
     */
    public <T,K,V> Map<K,V> listToMap(List<T> nodeList, IEntryMapper<T, K, V> mapper) throws Exception;

    /**
     * Permet la transformation d'une liste en une autre
     * @param list List<T1>
     * @return List<T2>
     */
    public <T1, T2> List<T2> transform(List<T1> list, IEntryTransformer<T1, T2> transformer) throws Exception;
}

L’implémentation qui correspond :

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ICollectionUtilsService;
import IEntryMapper;
import IEntryTransformer;
import IFilter;

/**
 * Filtrage et transformation des noeuds JCR
 *
 */
public class CollectionUtilsService implements ICollectionUtilsService {

    @Override
    public <U> List<U> filter(List<U> list, IFilter<U> filter) {
        List<U> newList = new ArrayList<U>();

        for (U item : list) {
            if (filter.accept(item)) {
                newList.add(item);
            }
        }

        return newList;
    }

    @Override
    public <T, K, V> Map<K, V> listToMap(List<T> list, IEntryMapper<T, K, V> mapper) throws Exception {
        Map<K,V> map = new HashMap<K, V>();

        for (T item : list) {
            map.put(mapper.getKey(item), mapper.getValue(item));
        }

        return map;
    }

    @Override
    public <T1, T2> List<T2> transform(List<T1> list, IEntryTransformer<T1, T2> transformer) throws Exception {
        List<T2> outputList = new ArrayList<T2>();

        for (T1 item : list) {
            outputList.add(transformer.transform(item));
        }

        return outputList;
    }

}

L’interface IEntryFilter va définir une méthode « accept ». L’implémentation de celle-ci va permettre, en fonction du type d’objet, de définir si pour un élément de la liste le filtre accepte l’élément pour le laisser dans la liste ou bien si il est rejeté.

public interface IEntryFilter<T> {
    /**
     * Accepte un noeud si il correspond au pattern
     * @param node Node
     * @return boolean
     */
    public boolean accept(T element);
}

L’interface IEntryMapper va définir deux méthodes : getKey et getValue qui vont, depuis un élément, renvoyer une clé ou une valeur pour pouvoir créer notre Map de sortie.

public interface IEntryMapper<T, K, V> {
    /**
     * Renvoit la clé
     * @return <K>
     */
    public K getKey(T element) throws Exception;

    /**
     * Renvoit la valeur
     * @return <V>
     */
    public V getValue(T element) throws Exception;
}

L’interface IEntryTransformer va définir une méthode « transform » qui va prendre un élément de notre liste initiale et le transformer dans un type correspondant à la liste de sortie.

public interface IEntryTransformer<T1, T2> {
    /**
     * Prend un element d'un type et le transforme dans un autre type (potentiellement le même)
     * @param item T1
     * @return T2
     * @throws Exception
     */
    public T2 transform(T1 item) throws Exception;
}

Avec ces quelques classes nous allons pouvoir effectuer beaucoup d’opérations sur des listes de types différents. L’intérêt étant que si l’on doit appliquer un nouveau filtre ou faire une nouvelle transformation sur des éléments, nous n’aurons plus à recopier du code inutile mais nous pourrons juste implémenter un nouveau IEntryFilter ou un IEntryTransformer spécialisés et les utiliser de la même manière. Voici des exemples d’implémentation de ces interfaces :

JCRNodesByTypeFilter : Filtre une liste de noeuds JCR en fonction du type de noeud :

public class JCRNodesByTypeFilter implements IEntryFilter<Node> {
    /**
     * Le logger de la classe
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(JCRNodesByTypeFilter.class);

    /**
     * Constructeur
     */
    public JCRNodesByTypeFilter(String type){
        this.nodeType = type;
    }

    /**
     * Type de noeud recherché
     */
    private String nodeType = null;

    /* (non-Javadoc)
     * @see IJCRNodeFilter#Accept(javax.jcr.Node)
     */
    @Override
    public boolean accept(Node noeud) {
        boolean retVal = false;
        Node tmpNode = null;

        try {
            if (noeud.getPrimaryNodeType().isNodeType(nodeType)) {
                // Si le type correspond, on accepte le noeud dans la liste de retour
                retVal = true;
            }

        } catch (RepositoryException e) {
            LOGGER.error("JCRSpecificTypesNodeFilter : Impossible de déterminer le type du noeud.");
        }

        return retVal;
    }
}

JCRPublishedNodeTypeFilter : Filtre une liste de noeuds JCR pour ne garder seulement les noeuds au statut « publié » :

public class JCRPublishedNodeTypesFilter implements IEntryFilter<Node> {
    /**
     * Le logger de la classe
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(JCRPublishedNodeTypesFilter.class);

    /**
     * Constructeur
     */
    public JCRPublishedNodeTypesFilter(){
    }

    /* (non-Javadoc)
     * @see IJCRNodeFilter#Accept(javax.jcr.Node)
     */
    @Override
    public boolean accept(Node noeud) {
        boolean retVal = false;

        try {
            // On recupere que les noeuds publiés
            if ("published".equals(noeud.getProperty("publication:currentState").getString())) {
                retVal = true;
            }

        } catch (RepositoryException e) {
            LOGGER.error("JCRSpecificTypesNodeFilter : Impossible de récupérer la propriété du noeud.");
        }

        return retVal;
    }
}

Pour les « transformer » et les « mapper » c’est la même chose. On crée un nouvelle implémentation qui fait un traitment spécifique voulu et qui l’applique à tous les éléments de notre collection.

Côté utilisation de notre service, nous avons si dessous un exemple d’une méthode qui va exécuter un « transformer » puis un « filtre » sur notre collection initiale. Exemple d’utilisation de notre « CollectionUtils » :

/**
 * Construction de la nouvelle Liste avec la methode filter
 *
 */
List<Node> listeTransformee = service.transform(listeNoeuds, new EntryTransformerFrozenNodeToNode());
return service.filter(listeTransformee, new JCRNodesByTypeFilter(typeContenu));

Enjoy !

Crash + eXo Platform – sizeOf JCR content command

Crash est un outil qui permet de se connecter à une JVM et de profiter de toutes les librairies chargées par celle-ci pour exécuter des scripts Groovy, naviguer dans le JCR …

Cet outil peut fonctionner de différentes manières :

  • En stantalone
  • En mode web, déployé en tant que webapp
  • Directement injecté par Spring

Pour notre cas nous allons utiliser la version web déployée dans un Tomcat pour se connecter au JCR de eXo Platform. 2 versions du mode web existent, une configurée pour se connecter à eXoPlatform, une plus générique.

Généralités pour débuter

Dans un premier temps nous allons voir comment on fait pour se connecter à Crash une fois celui-ci déployé dans le Tomcat d’eXo Platform.

Crash écoute par défaut sur le port 2000 pour ssh et 5000 pour telnet. Ces deux possibilités sont permises pour se connecter. Dans notre cas nous ferons la connexion en ssh, avec l’utilisateur root (mot de passe : gtn) :

ssh -p 2000 -l root localhost

où « localhost » est le nom du serveur hébergeant Tomcat.

On tombe alors sur l’invite de commandes Crash

   ______
 .~      ~. |`````````,       .'.                   ..'''' |         |
|           |'''|'''''      .''```.              .''       |_________|
|           |    `.       .'       `.         ..'          |         |
 `.______.' |      `.   .'           `. ....''             |         | 1.2.8

Ensuite, il faut se connecter au repository :

repo use container=portal

Et il faut se connecter au workspace (ici collaboration) :

ws login -u root -p gtn collaboration

A partir de ce moment la on peut naviguer dans le JCR et faire tout un tas d’opérations comme créer, supprimer, déplacer des noeuds.

Créer une nouvelle commande Crash

La création d’une nouvelle commande se fait via un script Groovy.

Dans le war on trouve dans WEB-INF un répertoire crash/commands qui contient 2 répertoires pour accueillir les scripts Groovy le premier contient les commandes générales (system) et le deuxième les commandes JCR.

Nous allons donc réaliser un script Groovy nommé « sizeof.groovy » et le placer dans le répertoire JCR.

Les nouveaux scripts et les modifications de scripts existants sont pris en compte à chaud.

Cette nouvelle commande comporte des options :

-t (type) : permet de filtrer sur un « jcr:primaryType »
-l (limit) : permet de limiter le nombre de résultats
-f (outPutFile) : permet de donner un chemin et nom de fichier qui stockera les résultats

et un paramètre obligatoire qui est le « jcr:path » a partir du quel on veut lancer la recherche.

Voici le code du script :


package crash.commands.jcr

import javax.jcr.query.Query

import org.crsh.text.ui.UIBuilder
import org.crsh.cli.Usage
import org.crsh.cli.Command
import org.crsh.cli.Man
import org.crsh.cli.Argument
import org.crsh.cli.Option
import org.crsh.cli.Required

@Usage("sizeOf JCR nodes command")
class sizeof extends org.crsh.jcr.command.JCRCommand {

@Usage("size of a single content")
@Command
public Object list(
@Option(names=["t","type"]) @Usage("jcr:primaryType") String type,
@Option(names=["l","limit"]) @Usage("the result limit") @Man("The number of nodes displayed, by default this value is equals to 5") Integer limit,
@Option(names=["f","outPutFile"]) @Usage("Path with name of the output file") String outPutFile,
@Argument @Usage("JCR path") String path) {

    // Default limit set to 5
    limit = limit ?: 5;

    assertConnected();

    def queryMgr = session.workspace.queryManager;

    // JCR Query to retrieve all the subnodes of the given path
    def statement = "select * from " + (type != null ? type : "nt:base") + " where jcr:path like '" + path + "/%'";

    // Exceution of the query
    def select = queryMgr.createQuery(statement, Query.SQL);
    def result = select.execute()
    def nodes = result.nodes
    def total = nodes.size

    // output result
    def stream = new StringBuilder()

    def builder = new UIBuilder();
    builder.node("The query matched " + total + " nodes") {
    def index = 0;
    def contentMap = [:]

    while (nodes.hasNext()) {
      def n = nodes.next()
      def nodeSize = 0

      // calculate the node size
      if (n.hasProperty("jcr:content/jcr:data")) {
        nodeSize = n.getProperty("jcr:content/jcr:data").getLength() / 1024
      }

      contentMap.put(n.path,nodeSize)

      index++
      if (limit != null && index >= limit) {
        break;
      }
    }

    // Sort the new map from the biggest to the smallest
    contentMap = contentMap.sort{a,b -> b.value <=> a.value}
    def chaine
    def file

    for (item in contentMap){
      chaine = item.key + " : " + item.value + " Ko"
      stream.append(chaine + "\r\n")
      label(chaine)
    }
 }

 // Store in the file
 if (outPutFile != null) {
   System.out.println("Output file : " + outPutFile)

   file = new File(outPutFile)
   file.write(stream.toString())
 }

 return builder;
 }
}

Une fois ce script déployé, un lancement de la commande « help » permet de s’assurer qu’il est bien disponible dans Crash.


% help
Try one of these commands with the -h or --help switch:

NAME DESCRIPTION
cd : changes the current node
commit : saves changes
cp : copy a node to another
dashboard
env : display the term env
filter : A filter for a stream of map
help : provides basic help
java : various java language commands
jdbc : JDBC connection
jmx : Java Management Extensions
jndi : Java Naming and Directory Interface
jpa : Java persistance API
jvm : JVM informations
log : logging commands
ls : list the content of a node
man : format and display the on-line manual pages
mixin : mixin commands
mv : move a node
node : node commands
pwd : print the current node path
repo : repository interaction commands
rm : remove one or several node or a property
rollback : ollback changes
selec : execute a JCR sql query
shell : shell related command
sizeof : sizeOf JCR nodes command
sleep : sleep for some time
sort : Sort a map
system : vm system properties commands
thread : JVM thread commands
version : versioning commands
ws : workspace commands
xpath : execute a JCR xpath query

Maintenant on peut aisément exécuter la commande suivante :

sizeof list -t nt:file -l 10 "/sites content/live/Contenus/MonRepertoireDeContenus"

En retour nous allons avoir un tableau indiquant les chemins des contenus et leurs poids, allant du plus lourd au plus léger.

% sizeof content -t nt:file -l 10 "/sites content/live/Contenus/Footer
The query matched 8 nodes
+-/sites content/live/Contenus/Footer/Footer simple/footer/default.html : 2.556640625 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer-authentification/default.html : 2.3564453125 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer/js/default.js : 0 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer-authentification/js/default.js : 0 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer/css/default.css : 0 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer/medias/images/illustration : 0 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer-authentification/css/default.css : 0 Ko
+-/sites content/live/Contenus/Footer/Footer simple/footer-authentification/medias/images/illustration : 0 Ko

La documentation de Crash est très bien faite. Vous pouvez trouver toutes les infos sur http://www.crashub.org

Enjoy !

JBoss/eXo Portal (Tomcat) : Valves et Filtres

Contexte :

Tomcat permet de d’intercepter les requêtes et les réponses. Cela peut s’avérer intéressant pour faire différents traitements tel que par exemple l’encodage des paramètres d’une requête (encodage systématique), la gestion propre d’un logout avec invalidation de la session… Les applications sont multiples.

Les Valves Tomcat :

La technologie des Valves est propre à Tomcat et n’existe pas forcement sur les autres serveurs d’application (En tout cas pas sous cette forme).

La Valve, lorsqu’elle est appelée, intercepte la requête Http avec et met a disposition les paramètres Request et Response de celle-ci. Elle étend la classe « org.apache.catalina.valves.ValveBase » et doit surcharger la méthode « invoke ».

On peut créer autant de Valves que l’on veut et celles-ci vont s’enchaîner lorsqu’on appelle la méthode « getNext().invoke ». Si cet appel est homis, aucune autre valve sera appelée.

Les Valves sont invoquées dans l’ordre de leurs déclaration dans le fichier Xml.


import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Invalidation des sessions
*/
public class LogoutValve extends ValveBase {
	private static final Logger LOGGER = LoggerFactory.getLogger(LogoutValve.class);

	@Override  public void invoke(Request pRequest, Response pResponse) throws IOException, ServletException {
		if (pRequest.getParameterMap().size() > 0
			&& pRequest.getParameterMap().containsKey("amp;portal:action")
			&& ((String[]) pRequest.getParameterMap().get("amp;portal:action"))[0].equals("Logout")) {
				// On invalide la session et on crée une nouvelle session
				LOGGER.info("Logout : Invlidation de la session !");
				pRequest.getSession(true).invalidate();
				pRequest.setRequestedSessionId(null);
				pRequest.clearCookies();

				// Create a new session and set it to the request
				Session newSession = pRequest.getSessionInternal(true);
				pRequest.setRequestedSessionId(newSession.getId());

				// Redirection vers l'accueil
				((HttpServletResponse) pResponse).sendRedirect(pRequest.getRequestURL().toString());
			} else {
				// Passage a la prochaine valve
				super.getNext().invoke(pRequest, pResponse);
			}
		}
	}
}

Paramétrage des Valves dans le portal.xml :

<Context path="/portal" docBase="portal" debug="0" reloadable="true" crossContext="true" privileged="true">
    <Valve className='org.toto.filtre.LogoutValve' characterEncoding='UTF-8' />
</context>

Javax.servlet.Filter

Les filtres du package « Javax » permettent un fonctionnement similaire. Lorsqu’on les paramètre dans le « web.xml » on leurs indique un « url-pattern » qui va définir sur quelle url le Filtre doit se déclencher. Du coup on peut, par rapport aux Valves, affiner leurs déclenchement et ne pas le rendre systématique.

Les filtres s’enchaînent aussi et on laisse la possibilité au développeur de continuer le chaînage des filtres ou d’intérompre celle-ci. Le 3e paramètre, FilterChain, permet donc de gérer la pile d’appel des filtres.

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class LogoutFilter implements Filter {
    @Override
    public void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pFilterChain) throws IOException, ServletException {
        // On invalide la session et on crée une nouvelle session
        ((HttpServletRequest) pRequest).getSession().invalidate();
        ((HttpServletRequest) pRequest).getSession(true);
    }
}

Paramétrage des filtres dans le web.xml :


<filter>
    <filter-name>logoutFilter</filter-name>
    <filter-class>org.toto.web.LogoutFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>logoutFilter</filter-name>
    <url-pattern>/logout</url-pattern>
</filter-mapping>

Tomcat : Redirection des loggeurs vers LogBack

Le but est de rediriger l’ensemble des loggeurs d’un Tomcat vers LogBack.

Pour ce faire, il faut supprimer les librairies suivantes dans Tomcat/lib :
– commons-logging-1.1.1.jar
– log4j-1.2.1.4.jar
Les numéros de version sont la à titre indicatif mais peuvent varier en fonction des versions de Tomcat.

Ensuite, il faut remplacer les librairies par les bridges suivants :
– jcl-over-slf4j-1.5.8.jar
– jul-to-slf4j-1.5.8.jar
– log4j-over-slf4j-1.5.8.jar

Il faut rajouter les librairies LogBack (toujours dans Tomcat/lib) :
– logback-classic-0.9.17.jar
– logback-core-0.9.17.jar

Etape suivante, création d’un fichier de paramétrage de LogBack. Dans l’exemple on l’appelle « logback.xml » et on va le placer dans Tomcat/conf :

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator" />
 <contextName>exo-plateform</contextName>

 <jmxConfigurator />

 <appender name="FILE-APP" class="ch.qos.logback.core.FileAppender">
 <File>${catalina.home}/logs/logback.log</File>
 <layout class="ch.qos.logback.classic.PatternLayout">
 <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS,Europe/Paris} [%thread] %-5level %logger{36} - %msg %xEx%n</Pattern>
 </layout>
 </appender>

 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
 <layout class="ch.qos.logback.classic.PatternLayout">
 <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS,Europe/Paris} [%thread] %-5level %logger{36} - %msg %xEx%n</Pattern>
 </layout>
 </appender>

 <root level="info">
 <appender-ref ref="FILE-APP" />
 <appender-ref ref="CONSOLE" />
 </root>
</configuration>

Dernière étape, il faut modifier le « catalina.properties » pour prendre en compte notre fichier de configuration de LogBack :

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,D://Java/applicatif/conf/langues,D://Java/applicatif/libs/*.jar,${catalina.home}/conf/logback

Pour éviter d’avoir des fichiers logs résiduels à 0ko, il faut supprimer le fichier « logging.properties » qui ne sera désormais plus utilisé.

Pour terminer, on peut rediriger aussi tous les logs Tomcat de base dans LogBack. Pour ça, il faut télécharger les librairies suivantes des « EXTRAS » de Tomcat :
– tomcat-juli.jar
– tomcat-juli-adapters.jar

Tomcat-Juli remplace la librairie initiale du même nom dans Tomcat/bin et les « adapters » vont dans Tomcat/lib. Cette librairie n’existe pas, donc n’est pas écrasée. Ensuite il faut rajouter dans le fichier logback.xml les loggeurs suivants :

<logger name="org.apache.catalina" level="info">
 <appender-ref ref="CATALINA" />
</logger>

<logger name="org.apache.catalina.core.ContainerBase.[Catalina].[localhost]" level="info">
 <appender-ref ref="LOCALHOST" />
</logger>

<logger
 name="org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager]" level="info">
 <appender-ref ref="MANAGER" />
</logger>

<logger
 name="org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/admin]" level="info">
 <appender-ref ref="ADMIN" />
</logger>

<logger
 name="org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager]" level="info">
 <appender-ref ref="HOST-MANAGER" />
</logger>

Normalement, tout devrait être correctement redirigé.