Import CSV (Adapter) Spécifique

Bonjour à tous,
J’espère que vous allez bien,
J’ai rencontre une difficulté sur simplicité, je voudrais créer un adapter CSV spécifique qui va importer un excel renseignant 3 Business Object dans ma BD. pour cela j’ai créer un adapter spécifique dans Operation -> Adapter, voici le code :

package com.simplicite.adapters.Factur_projetDD;

import java.util.*;
import com.simplicite.util.*;
import com.simplicite.util.integration.*;

import com.simplicite.util.tools.*;

/**
 * Adapter InvEvtCSVAdapter
 */
public class InvEvtCSVAdapter extends CSVLineBasedAdapter {
	private static final long serialVersionUID = 1L;
	private ObjectDB exampleObject = getGrant().getTmpObject("contrat");

	// Good practice : use specific exception class
	private static class InvEvtCSVAdapterException extends Exception{
		public InvEvtCSVAdapterException(String message){
			super(message);
		
		}
	}
	

	
	@Override
	public String processValues(long lineNumber, String[] values){	
		// Good practice: handle errors with exceptions		
		try{
			// add some logs to the .log file (added in the imports supervisor object)
			appendLog("=== Processing line #"+lineNumber+" : "+Arrays.toString(values));
			processWithExceptions(lineNumber, values);
		}
		catch(InvEvtCSVAdapterException e){
			// add some logs to the .err file (added in the imports supervisor object)
			appendError("=== Error with line #"+lineNumber+" : "+Arrays.toString(values));
			appendError(e);

			// change import status to impact the supervisor object
			setStatus(SystemXML.SUPERVISOR_STATUS_IMPORT_ERROR);
		}

		// returned String gets added to a XML subsequently imported.
		// in this case we do not append anything to XML subsequently imported,
		// as we directly create the objects instead
		return null; 
	}

	public void postProcess(){
		appendLog("End Process with status "+getStatus());
		// to generate a subsequently imported XML, call super.postProcess()
		// doing so will add a closing <simplicite> tag
	}

	public void processWithExceptions(long lineNumber, String[] values) throws InvEvtCSVAdapterException{
		String createMsg;
		synchronized(exampleObject){
			exampleObject.resetValues(true);
		exampleObject.setFieldValue("numContrat", values[0]);
		exampleObject.setFieldValue("typeContrat", values[1]);
		
			createMsg = exampleObject.create();
		}
		if(!Tool.isEmpty(createMsg)){
			throw new InvEvtCSVAdapterException(createMsg);
		}
	}

}

J’ai l’erreur Adapter :

Adapter error: null

quand j’essaye d’importer un exemple de csv avec uniquement un Business object et 2 object field (numContrat et typeContrat)

J’utilise l’adapter grace à l’external object ImportXML, voici mes question svp :

  1. Est t-il la bonne façon de procéder ?
  2. Vaut t-il le coup de créer une interface custom plus user-friendly pour utiliser l’adapter spécifique ?
  3. Si oui, ça sera quoi le workflow de développement ?

Je vous remercie d’avance, et vous souhaitant de bonnes fêtes de fin d’année à tous

Bonjour

Adapter error: null => Vous avez visiblement un “null pointer exception” non catché qui se produit quelque part dans votre code => ajoutez des traces, des blocs try/catch, etc. ou, si vous le pouvez, mettez vous en remote debug depuis un IDE (cf https://vimeo.com/475908811).

Pour ce qui est de l’interface d’appel de l’adapter, oui l’interface utilisateur “standard” c’est la page d’import XML avec sélection de l’adapter mais rien ne vous empêche d’ajouter une page specifique = un “objet externe” (dans le vocabulaire Simplicité, cf. https://docs.simplicite.io/documentation/01-core/externalobject-code-examples.md#responsive) ou d’invoquer l’import via l’adapter via une action (avec paramètre fichier) d’un objet métier, etc. en fonction de ce qui conviendra le mieux en termes d’UX

Merci pour votre retour,
Effectivement, j’ai déplacé le exampleObject = getGrant().getTmpObject("Contrat"); à la fonction ```
processValues
ça donne :

	@Override
	public String processValues(long lineNumber, String[] values){	
		// Good practice: handle errors with exceptions		
		try{
			exampleObject = getGrant().getTmpObject("Contrat");
	
			// add some logs to the .log file (added in the imports supervisor object)
			appendLog("=== Processing line #"+lineNumber+" : "+Arrays.toString(values));
			processWithExceptions(lineNumber, values);
		}
		catch(InvEvtCSVAdapterException e){
			// add some logs to the .err file (added in the imports supervisor object)
			appendError("=== Error with line #"+lineNumber+" : "+Arrays.toString(values));
			appendError(e);

			// change import status to impact the supervisor object
			setStatus(SystemXML.SUPERVISOR_STATUS_IMPORT_ERROR);
		}

		// returned String gets added to a XML subsequently imported.
		// in this case we do not append anything to XML subsequently imported,
		// as we directly create the objects instead
		return null; 
	}

et ça marche, sauf qu’il ne fait pas un update automatiquement quand je charge un csv avec des enregistrement déjà existants, il crée des doublant (l’objet possède 1 clé fonctionnelle ).

Avez vous une idée svp ?
Merci d’avance

Vous devez implémenter la logique de “create or update”:

  1. search si un record existe sur la base des clés fonctionnelles
  2. si oui => getForUpdate avec le row ID trouvé
  3. si non => getForCreate
  4. validateAndSave

Genre comme ça : https://gist.github.com/dazoulay-simplicite/ee17577dc228babcd405b0fed165f80f

Merci pour les précision et l’exemple, j’ai fini par implémenter l’import.

Bonjour,

Je me permets d’ajouter qu’il n’est pas optimisé de faire le getGrant().getTmpObject("Contrat"); dans le hook processValues qui est appelé à chaque ligne du CSV, vous recréez l’objet à chaque ligne. Dans l’exemple sur lequel vous vous êtes basé cette opération est faite dans le hook preProcess.

Le nullpointer que vous obteniez vient du fait que vous avez essayé de faire cette opération lors de l’initialisation de la classe, ce qui n’est pas possible puisque getGrant() n’est pas statique et dépend de l’utilisateur. Si c’est une erreur de compréhension et non d’inattention, ne pas approfondir ces sujets purement Java risque d’être problématique. ;)

Bonjour @scampano ,
Merci pour votre retour, j’ai beau essayé d’implementer le getGrant() dans le preprocess mais cela ne marche pas, voici mon code qui ne fonctionne pas, à l’import rien ne se passe et aucun log, peux tu faire des suggestions s’il te plait ? (cela marche bien quand je mets les getGrant() dans le processValues)

package com.simplicite.adapters.Factur_projetDD;

import java.util.*;
import com.simplicite.util.*;
import com.simplicite.util.integration.*;

import com.simplicite.util.tools.*;

import com.simplicite.util.tools.JSONTool;
import org.json.JSONObject;

/**
 * Adapter InvEvtCSVAdapter
 */
public class InvEvtCSVAdapter extends CSVLineBasedAdapter {
	private static final long serialVersionUID = 1L;
	private ObjectDB contratBo ;
	private ObjectDB chefprojetBo ;
	private ObjectDB projetBo ;
	private ObjectDB factureBo ;
	
	// Good practice : use specific exception class
	private static class InvEvtCSVAdapterException extends Exception{
		public InvEvtCSVAdapterException(String message){
			super(message);
		
		}
	}
	
	public String preProcess(){
			// set CSV separator
			setSeparator(';'); 
			contratBo = getGrant().getTmpObject("Contrat");
			chefprojetBo = getGrant().getTmpObject("ChefProjet");
			projetBo = getGrant().getTmpObject("Projet");
			factureBo = getGrant().getTmpObject("Facture");
	
			// to generate a subsequently imported XML, call super.preProcess()
			// doing so will add a starting <simplicite> tag
			return null;
		}
		
	@Override
	public String processValues(long lineNumber, String[] values){	
		// Good practice: handle errors with exceptions		
		
		
		try{
			// add some logs to the .log file (added in the imports supervisor object)
			appendLog("=== Processing line #"+lineNumber+" : "+Arrays.toString(values));
			
		
		if (lineNumber>1)  // skip header
			processWithExceptions(lineNumber, values);
		}
		catch(InvEvtCSVAdapterException e){
			// add some logs to the .err file (added in the imports supervisor object)
			appendError("=== Error with line #"+lineNumber+" : "+Arrays.toString(values));
			appendError(e);

			// change import status to impact the supervisor object
			setStatus(SystemXML.SUPERVISOR_STATUS_IMPORT_ERROR);
		}

		// returned String gets added to a XML subsequently imported.
		// in this case we do not append anything to XML subsequently imported,
		// as we directly create the objects instead
		return null; 
	}


	public void processWithExceptions(long lineNumber, String[] values) throws InvEvtCSVAdapterException{
    
    	synchronized(factureBo){	
	    	synchronized(projetBo){	
		    	synchronized(chefprojetBo){	
					synchronized(contratBo){
						
					try {
					//Create BO Tool for each BO
					BusinessObjectTool contratBot = new BusinessObjectTool(contratBo); // or contratBo.getTool();
					BusinessObjectTool chefprojetBot = new BusinessObjectTool(chefprojetBo);
					BusinessObjectTool projetBot = new BusinessObjectTool(projetBo);
					BusinessObjectTool factureBot = new BusinessObjectTool(factureBo);
				
					//Start Processing 
					
					//Processing for "Projet"
					List<String[]> rs = projetBot.search(new JSONObject()
					.put("numero_projet", values[3]));
				
					if (rs.size() == 1) // 1 found = update
						projetBo.setValues(rs.get(0), true);	
					
					else if (rs.size() == 0) // 0 found => create
						{
						projetBo.resetValues(true); 
						
						//Processing for "contrat"
						rs = contratBot.search(new JSONObject().put("numContrat", values[0]));
					
						if (rs.size() == 1) // 1 found = update
						contratBo.setValues(rs.get(0), true);
						
						else if (rs.size() == 0) // 0 found => create
						contratBo.resetValues(true); //
					
						else // Should never happen if search is done using all functional keys
						throw new Exception(rs.size() + " contact records found");
						
						contratBo.setFieldValue("numContrat", values[0]);
						contratBo.setFieldValue("typeContrat", values[7]);	  
						contratBot.validateAndSave();
						
						//Processing for "ChefProjet"
						rs = chefprojetBot.search(new JSONObject()
						.put("nom", values[6].split(",[ ]*")[0])
						.put("prenom",values[6].split(",[ ]*")[1])
						);
					
						if (rs.size() == 1) // 1 found = update
						chefprojetBo.setValues(rs.get(0), true);	
						
						else if (rs.size() == 0) // 0 found => create
						chefprojetBo.resetValues(true); // 
					
						else // Should never happen if search is done using all functional keys
						throw new Exception(rs.size() + " records found");
						
						chefprojetBo.setFieldValue("nom", values[6].split(",[ ]*")[0]);
						chefprojetBo.setFieldValue("prenom", values[6].split(",[ ]*")[1]);	  
						chefprojetBot.validateAndSave();
						}
					else // Should never happen if search is done using all functional keys
						throw new Exception(rs.size() + " project chief records found");
			
				
					//Create/Update "Projet"
					projetBo.setFieldValue("numero_projet", values[3]);
					projetBo.setFieldValue("orgaProjet", values[2] );	  
					projetBo.setFieldValue("nomProjet", values[4] );
					projetBo.setFieldValue("nomTacheSup", values[5]);
					projetBo.setFieldValue("Projet_Contrat_id", contratBo.getFieldValue("row_id"));
					projetBo.setFieldValue("Projet_ChefProjet_id",chefprojetBo.getFieldValue("row_id") );
					projetBo.setFieldValue("numLigneContrat", values[1] );	  
				
					projetBot.validateAndSave();
					
				
	    			//Create/Update "Facture"
					List<String[]> rs_fac = factureBot.search(new JSONObject()
					.put("numEvt", values[10]));
					
					if (rs_fac.size() == 1) // 1 found = update
					factureBo.setValues(rs_fac.get(0), true);	
						
					else if (rs_fac.size() == 0) // 0 found => create
					factureBo.resetValues(true); // 
					
					else // Should never happen if search is done using all functional keys
					throw new Exception(rs_fac.size() + " invoice records found");
					
			
					
					//Date mapping from DD/MM/YYYY to YYYY/MM/DD
					 
					String dbDate = values[9].split("/")[2] +"-"+ values[9].split("/")[1]+ "-"+ values[9].split("/")[0];
				
					factureBo.setFieldValue("dateFinEvenement", dbDate );
					factureBo.setFieldValue("description", values[11] );
					
					//replace the comma by dot anb delete space from "montantNonFacture"
						
					String montantNonFacture = values[12].replaceAll(",",".");
				  
				
					factureBo.setFieldValue("montantNonFacture", montantNonFacture.replaceAll("\\s+","") );
					
					factureBo.setFieldValue("Facture_Projet_id", projetBo.getFieldValue("row_id") );
				
					
					factureBo.setFieldValue("reportFacturation", values[14] );
					factureBo.setFieldValue("commentaireFacture", values[15] );
					
					//replace the comma by dot anb delete space from "montantAFacturer"
					String montantAFacturer = values[16].replaceAll(",",".");
					   
					
					
					factureBo.setFieldValue("montantAFacturer", montantAFacturer.replaceAll("\\s+","") );
					factureBo.setFieldValue("statutEvtFacture", "REP" );
					
					//Status related to "type contrat" on the first import
					if (rs_fac.size() == 0 && (values[7].equals("Date à date") || values[7].equals("Epuisements de jours")) )
					factureBo.setFieldValue("statutEvtFacture", "FAC" );
					else 
					factureBo.setFieldValue("statutEvtFacture", "AFAC" );
				
					//Status related to "description" on evry import
					if (values[11].equals("pas de base factu") || values[11].equals("reste à facturer"))
					factureBo.setFieldValue("statutEvtFacture", "ATT" );
				
				
					factureBo.setFieldValue("TypeEvent", values[8] );
					factureBo.setFieldValue("numEvt", values[10]);
					
					
					factureBot.validateAndSave();
				
					
						
					
				
					}catch (Exception e) {
					
					}
		    	}
	    	}
	    	}
		}
					
				
			
						
				
	    		
	    	
	    	
	
	}
	

}

Merci d’avance,

Bonjour, vous ne pouvez pas ne pas traîter les exceptions comme vous l’avez fait! S’il y en a eu une, c’est normal que rien ne soit loggé vu ce que vous avez écrit…

Utilisez plutôt ceci:

catch (Exception e) {
	AppLog.error(InvEvtCSVAdapter.class, "InvEvtCSVAdapter", e.getMessage(), e, Grant.getSystemAdmin());
	throw new InvEvtCSVAdapterException("INT_ADP_ERR_PROCESSING_LINE: "+e.getMessage());
}

PS: Par ailleurs, je recommande vivement l’utilisation d’un analyseur de code de type Sonar qui remonte automatiquement ce genre de mauvaises pratiques de développement Java, en cas de faute d’inattention. Votre fonction processWithExceptions mériterait aussi d’être décomposée par souci de lisibilité :wink: