Unable to get row ID --> cache ajax persistent?

Tags: #<Tag:0x00007f9e5154b338>

Bonjour,
nous avons un objet BCSIRCMModificationRequest, composé de 26 champs, correspondant à des demandes d’évolutions.
Une demande a un type qui définit quels sont les champs a vraiment renseignés.
Côte back, aucun soucis , l’utilisateurs peut créer autant de demandes qu’il veut et en alternant les types (actuellement il y en a deux) :
Nous avons développé une page publique permettant à utilisateur non connecté de soumettre également des demandes. Voici le code simplifié :

var BCSIRCMPublicModificationRequest = typeof BCSIRCMPublicModificationRequest !== "undefined" ? BCSIRCMPublicModificationRequest : (function($) {
	var appSimplicite, translationMap, modificationRequest, application, scope;
	var $main, $popup, manager, userId;

	function init(parameterMap) {
		appSimplicite = new Simplicite.Ajax(ROOT, "uipublic");
		appSimplicite.setErrorHandler(showSimpliciteError);
		appSimplicite.getTexts(function (texts) {
			translationMap = texts;
			
			$main = $("#main");
			modificationRequest = appSimplicite.getBusinessObject("BCSIRCMModificationRequest");
			modificationRequest.getMetaData(function() {
				// Construction d'un formulaire HTML
				// Construction d'un bouton dont le clique appelle la fonction submitRequest;
			});
		});
	}
	 
	function submitRequest(parameterMap) {
		modificationRequest.getForCreate(function() {
			modificationRequest.item.RCMRequestType = parameterMap["REQUEST_TYPE"];
			modificationRequest.item.RCMLang = parameterMap["LANG"];
			modificationRequest.item.RCMApplicant = userId;
			modificationRequest.item.RCMRequestDate = (new Date()).toISOString().replace('T', ' ').substring(0, 19);
			if (modificationRequest.item.RCMRequestType == "FLOW" || parameterMap["REQUEST_TYPE"] == "FLOW_DEL") {
				modificationRequest.item.RCMSenderApplication = $("#RCMSenderApplication").val();
				modificationRequest.item.RCMReceiverApplication = $("#RCMReceiverApplication").val();
				modificationRequest.item.RCMMessageComment = $("#RCMMessageComment").val();
				modificationRequest.item.RCMMessageId = parameterMap["FLOW_ID"];
			 } else if (modificationRequest.item.RCMRequestType == "SCOPE" || parameterMap["REQUEST_TYPE"] == "SCOPE_DEL") {
				modificationRequest.item.RCMApplication = $("#RCMApplication").val();
				modificationRequest.item.RCMScope = $("#RCMScope").val();
				modificationRequest.item.RCMScopeDate = $("#RCMScopeDate").val();
			 }
			modificationRequest.create(function(result) {
				showSuccessMessage(translationMap["SUCCESS_CREATE_REQUEST"]);
				hideErrorMessage();
				$main.html("");
			 });
		});
    }

	return { init : init };
})(jQuery);

Nous rencontrons un problème systématique lors de la création d’une demande d’évolution dont le type est différent de la première demande.
Scénario :

  1. Clearer le cache de la plateforme
  2. Aller sur la page de demande de modification de type FLOW
    image
  3. Renseigner et soumettre la demande ==> ça passe (On peut répéter les étapes 2 et 3 pour créer plusieurs demandes sans problème)
    image
  4. Aller sur la page de demande de modification de type SCOPE
    image
  5. Renseigner et soumettre la demande ==> ça ne passe pas mais la demande est bien enregistrée
    image
  6. Ouvrir un nouvel onglet sur ui sur la plateforme et clearer la plateforme.
  7. Resoumettre la demande ==> ca passe. Par contre si je vas sur le formulaire de type FLOW, j’obtiens cette fois l’erreur.

Si à la place de l’étape 6, au lieu de clearer la plateforme, je crée une demande de type SCOPE dans la partie ui classique, j’obtiens le même résultat à l’étape 7.

Nous ne constatons aucun log d’erreur.
Notre objet a bien une clé fonctionnelle. Elle est composée d’un Datetime et d’une FK vers l’objet User.
Nous avons bien recréé les index.

Avez-vous une piste pour cette erreur ?
Pour info, une même page publique ne peut pas soumettre 2 demandes à la suite. Il faut soit recharger la page, soit ouvrir un second onglet.

Voici le code simplifé de l’objet :

public class BCSIRCMModificationRequest extends ObjectDB {
	private static final long serialVersionUID = 1L;

	@Override
	public void initCreate() {
		resetUpdatableValueField();
		this.getField("RCMRequestType").setUpdatable(true);
		this.getField("RCMApplicant").setUpdatable(true);
		this.getField("RCMRequestDate").setUpdatable(true);
		if (!"public".equals(this.getGrant().getLogin())) {
			this.getField("RCMApplicant").setValue(this.getGrant().getUserId(this.getGrant().getLogin()));
			this.getField("RCMRequestDate").setValue(new Date());
		}
		
		this.setSave(true);
		this.setSaveAndClose(true);
	}
	
	public void resetUpdatableValueField() {
		for (ObjectField objectField : this.getFields()) {
			if ("SUBMIT".equals(this.getStatusField().getValue())) {
				objectField.setUpdatable(objectField.getUpdatableDefault());
			} else {
				objectField.setUpdatable(false);
			}
		}
	}

	@Override
	public List<String> preValidate() {
		List<String> messageList = new ArrayList<>();
		
		for (ObjectField objectField : this.getFields()) {
			objectField.setRequired(objectField.getRequiredDefault());
		}
		if ("FLOW".equals(this.getFieldValue("RCMRequestType")) 
			|| "FLOW_DEL".equals(this.getFieldValue("RCMRequestType"))) {
			messageList.addAll(preValidateMessageRequest("FLOW_DEL".equals(this.getFieldValue("RCMRequestType"))));
		}
		if ("SCOPE".equals(this.getFieldValue("RCMRequestType")) 
			|| "SCOPE_DEL".equals(this.getFieldValue("RCMRequestType"))) {
			messageList.addAll(preValidateScopeRequest("SCOPE_DEL".equals(this.getFieldValue("RCMRequestType"))));
		}
		return messageList;
	}

	public List<String> preValidateMessageRequest(boolean deletion) {
		if (StringUtils.isBlank(this.getFieldValue("RCMSenderApplication"))) {
			this.getField("RCMApplication").setRequired(false);
		} else {
			this.getField("RCMApplication").setValue(this.getFieldValue("RCMSenderApplication"));
		}
		this.getField("RCMSenderApplication").setRequired(true);
		this.getField("RCMReceiverApplication").setRequired(true);
		this.getField("RCMMessageComment").setRequired(!deletion);
		this.getField("RCMMessageId").setRequired(deletion);
	
		return new ArrayList<>();
	}

	public List<String> preValidateScopeRequest(boolean deletion) {
		this.getField("RCMScope").setRequired(true);
		this.getField("RCMScopeDate").setRequired(!deletion);
		return new ArrayList<>();
	}
	
	@Override
	public List<String> postValidate() {
		List<String> messageList = new ArrayList<>();
		if (!"SUBMIT".equals(this.getStatusField().getValue()) && "SUBMIT".equals(this.getStatusField().getOldValue())){
			if ("ACCEPTED".equals(this.getStatusField().getValue())) {
				ObjectDB object = prepareObjectDB(messageList);
				if (object != null) {
					List<String> objectMessageList = object.validate();
					if (objectMessageList != null && !objectMessageList.isEmpty()) {
						messageList.add(Message.formatError("RCM_CU_" + this.getFieldValue("RCMRequestType").replace("_DEL", "") + "_ERROR", null, null));
					}
				}
			}
			this.getField("RCMValidator").setValue(this.getGrant().getUserId(this.getGrant().getLogin()));
		}
		return messageList;
	}

	@Override
	public String preCreate() {
		this.getField("RCMDescription").setValue(buildDescription());
		return null;
	}
	
	public String buildDescription() {
		StringBuilder sb = new StringBuilder();
		this.getFields().stream()
			.filter(field -> field.isVisibleOnForm() 
								&& !StringUtils.isBlank(field.getValue()) 
								&& field.getType() != 0 
								&& !(this.getFieldValue("RCMRequestType").startsWith("FLOW") && field.getInput().startsWith("RCMApplication")))
			.forEach(field -> {
				sb.append(field.getDisplay()).append(" : ").append(field.getDisplayValue()).append("\n");
			});
		return sb.toString();
	}
}

Cordialement
Amandine TRIDOU

[Platform]
Status=OK
Version=4.0.P24
BuiltOn=2020-07-01 16:42 (revision ca0a90ddbcc304cf5649bcbcda1bba00f665a37b)
Encoding=UTF-8
EndpointIP=21.0.9.3
EndpointURL=http://0e26dcd6c27b:8080
TimeZone=Europe/Paris
SystemDate=2020-07-08 14:55:29

La lib Ajax standard que vous utilisez est la même que celle qu’utilise la UI donc je ne pense pas que votre pb vienne de cette lib…

En général quand on a un message "Unable to get row ID xxx` c’est qu’une règle de gestion ou un search spec back fait que le record auquel on cherche à avoir accès (ex: juste après le create) n’est pas/plus accessible au user connecté (donc ici le user “public”).

Déjà avez vous fait du debug pas à pas dans votre code ?

Le code JS se debugge très facilement dans le debugger de votre navigateur (y compris dans la lib Ajax si besoin). Si besoin mettez un debugger; au début de votre fonction submitRequest.

function submitRequest(parameterMap) {
    debugger;
    (...)
});

Quand le debugger est actif sur votre navigateur cette instruction déclenche un point d’arret à l’endroit où elle est.

Ca vous permettra de voir l’item tel que valorisé par le getForCreate() et celui que retourne le create etc.

oui oui c’est la première chose que j’avais fais et il n’y a aucune différence.
Scénario :

  1. J’ai fait un demande de modification de type SCOPE --> Enregistrement sans problème. Id = 1
  2. Je vais sur la page de création de type FLOW
    modificationRequest.item après le get for create
{
    "row_id": "0",
    "RCMRequestStatus": "SUBMIT",
    "RCMRequestType": null,
    "RCMRequestDate": null,
    "RCMApplication": null,
    "RCMApplication__AppIRN": null,
    "RCMApplication__AppShortName": null,
    "RCMApplication__AppDomain": null,
    "RCMApplicant": null,
    "RCMApplicant__PersonCommonName": null,
    "RCMValidator": null,
    "RCMValidator__PersonCommonName": null,
    "RCMSenderApplication": null,
    "RCMSenderApplication__AppIRN": null,
    "RCMSenderApplication__AppShortName": null,
    "RCMReceiverApplication": null,
    "RCMReceiverApplication__AppIRN": null,
    "RCMReceiverApplication__AppShortName": null,
    "RCMMessageId": null,
    "RCMMessageComment": null,
    "RCMScope": null,
    "RCMScope__ScopeName": null,
    "RCMScope__ScopeNameEN": null,
    "RCMScopeDate": null,
    "created_dt": null,
    "created_by": null,
    "updated_dt": null,
    "updated_by": null,
    "RCMLang": "FR",
    "RCMComment": null,
    "RCMDescription": null
}

modificationRequest.item juste avant le create

{
    "row_id": "0",
    "RCMRequestStatus": "SUBMIT",
    "RCMRequestType": "FLOW",
    "RCMRequestDate": "2020-07-08 14:06:30",
    "RCMApplication": null,
    "RCMApplication__AppIRN": null,
    "RCMApplication__AppShortName": null,
    "RCMApplication__AppDomain": null,
    "RCMApplicant": "4706",
    "RCMApplicant__PersonCommonName": null,
    "RCMValidator": null,
    "RCMValidator__PersonCommonName": null,
    "RCMSenderApplication": "20417",
    "RCMSenderApplication__AppIRN": null,
    "RCMSenderApplication__AppShortName": null,
    "RCMReceiverApplication": "16040",
    "RCMReceiverApplication__AppIRN": null,
    "RCMReceiverApplication__AppShortName": null,
    "RCMMessageComment": "ma requête sans réinit le cache",
    "RCMScope": null,
    "RCMScope__ScopeName": null,
    "RCMScope__ScopeNameEN": null,
    "RCMScopeDate": null,
    "created_dt": null,
    "created_by": null,
    "updated_dt": null,
    "updated_by": null,
    "RCMLang": "EN",
    "RCMComment": null,
    "RCMDescription": null
}

–> erreur enable to get ID…
3. Je reset le cache
4. Je resoumets la demande (depuis la même page) --> Enregistrement sans problème. Id = 3
modificationRequest.item après le get for create

{
    "row_id": "0",
    "RCMRequestStatus": "SUBMIT",
    "RCMRequestType": null,
    "RCMRequestDate": null,
    "RCMApplication": null,
    "RCMApplication__AppIRN": null,
    "RCMApplication__AppShortName": null,
    "RCMApplication__AppDomain": null,
    "RCMApplicant": null,
    "RCMApplicant__PersonCommonName": null,
    "RCMValidator": null,
    "RCMValidator__PersonCommonName": null,
    "RCMSenderApplication": null,
    "RCMSenderApplication__AppIRN": null,
    "RCMSenderApplication__AppShortName": null,
    "RCMReceiverApplication": null,
    "RCMReceiverApplication__AppIRN": null,
    "RCMReceiverApplication__AppShortName": null,
    "RCMMessageId": null,
    "RCMMessageComment": null,
    "RCMScope": null,
    "RCMScope__ScopeName": null,
    "RCMScope__ScopeNameEN": null,
    "RCMScopeDate": null,
    "created_dt": null,
    "created_by": null,
    "updated_dt": null,
    "updated_by": null,
    "RCMLang": "FR",
    "RCMComment": null,
    "RCMDescription": null
}

modificationRequest.item juste avant le create

{
    "row_id": "0",
    "RCMRequestStatus": "SUBMIT",
    "RCMRequestType": "FLOW",
    "RCMRequestDate": "2020-07-08 14:08:56",
    "RCMApplication": null,
    "RCMApplication__AppIRN": null,
    "RCMApplication__AppShortName": null,
    "RCMApplication__AppDomain": null,
    "RCMApplicant": "4706",
    "RCMApplicant__PersonCommonName": null,
    "RCMValidator": null,
    "RCMValidator__PersonCommonName": null,
    "RCMSenderApplication": "20417",
    "RCMSenderApplication__AppIRN": null,
    "RCMSenderApplication__AppShortName": null,
    "RCMReceiverApplication": "16040",
    "RCMReceiverApplication__AppIRN": null,
    "RCMReceiverApplication__AppShortName": null,
    "RCMMessageComment": "ma requête après réinit le cache",
    "RCMScope": null,
    "RCMScope__ScopeName": null,
    "RCMScope__ScopeNameEN": null,
    "RCMScopeDate": null,
    "created_dt": null,
    "created_by": null,
    "updated_dt": null,
    "updated_by": null,
    "RCMLang": "EN",
    "RCMComment": null,
    "RCMDescription": null
}

résultat du create

{
    "row_id": "3",
    "RCMRequestStatus": "SUBMIT",
    "RCMRequestType": "FLOW",
    "RCMRequestDate": "2020-07-08 14:08:56",
    "RCMApplication": "20417",
    "RCMApplication__AppIRN": "IRN-70538",
    "RCMApplication__AppShortName": " SAP ERP 6.0 EHP8",
    "RCMApplication__AppDomain": "T05",
    "RCMApplicant": "4706",
    "RCMApplicant__PersonCommonName": "TRIDOU Amandine",
    "RCMValidator": null,
    "RCMValidator__PersonCommonName": null,
    "RCMSenderApplication": "20417",
    "RCMSenderApplication__AppIRN": "IRN-70538",
    "RCMSenderApplication__AppShortName": " SAP ERP 6.0 EHP8",
    "RCMReceiverApplication": "16040",
    "RCMReceiverApplication__AppIRN": "IRN-68521",
    "RCMReceiverApplication__AppShortName": "BCSI",
    "RCMMessageId": null,
    "RCMMessageComment": "ma requête après réinit le cache",
    "RCMScope": null,
    "RCMScope__ScopeName": null,
    "RCMScope__ScopeNameEN": null,
    "RCMScopeDate": null,
    "created_dt": "2020-07-08 16:08:57",
    "created_by": "public",
    "updated_dt": "2020-07-08 16:08:57",
    "updated_by": "public",
    "RCMLang": "EN",
    "RCMComment": null,
    "RCMDescription": "Type : Create or modify a flow\nApplicant Full name : TRIDOU Amandine\nSender application IRN : IRN-70538\nSender application Short name :  SAP ERP 6.0 EHP8\nReceiver application IRN : IRN-68521\nReceiver application Short name : BCSI\nMessage description : ma requête après réinit le cache\nLang : EN\n"
}

Le clear cache a “corrigé” le problème c’est qu’il y a bien quelque chose qui reste mémoire, non ?

Le clear cache ne travaille que sur le cache coté serveur. Il a pour effet de revenir à la définition statique de l’objet (donc y compris les éventuelles règles du hook statique postLoad et les contraintes de type “statique”), il reset les filtres courants, annule les effets du code dynamique et/ou des contraintes dynamiques qui ont potentiellement altéré votre objet, etc.

Donc je reste sur ma sur ma suspicion initiale qui est que quelque chose coté serveur (des hooks pre/post ? des contraintes ?) positionne sur votre objet un filtre “dur” (i.e. une searchspec SQL) et/ou des filtres et/ou des droits dynamiques qui font que le record n’est plus accessible après le create.

L’autre cas classique qui aboutit à un “Unable to get row ID xxx” c’est quand on joue sur le caractère obligatoire/facultatif s’une FK cf. nos explications à ce post Le count d'objets affichés n'est pas identique à ceux en base (c’est ici un autre symptôme mais c’est la même histoire => rendre une FK obligatoire dynamiquement peut rendre les records - qui ont null dans cette FK - invisibles)

Bref, regardez de près le code et/ou les contraintes “douteux” (au sens de “altérant la définition de votre objet”) sur votre objet. Inhibez/reactivez progressivement ces éléments jusqu’à cerner la règle qui a un effet néfaste. etc.

Bonjour David,
nous avons aussi pensé qu’il pourrait s’agir d’un filtre ou métadata résiduelle dans le cache serveur de la session du user public. Le fait d’éditer la liste en mode connecté (session privée connectée) permet d’ailleurs aussi de débloquer la situation du user public comme si l’action de la session privée avait invalidé le cache de la session public (car l’objet métier a été modifié).

Par ailleurs, c’est le même user “public” qui est utilisé dans toutes les instances de la page front externe et toutes ces instances utilisent peut-être la même session serveur (et donc le même cache). Est-ce que c’est bien ce qui se passe ou bien devons-nous nous assurer de cloisonner les caches des sessions public coté serveur (en gérant par exemple le cookie de session si il existe) ?

Clairement, s’agissant de mise à jour, plutôt que le user public je vous recommande d’utiliser un vrai user technique.

Cf. les frontends de la demo qui utilisent un user technique “website” à dessein

Merci @david pour votre réponse.
J’ai trouvé la solution mais elle me laisse perplexe.
Lors de l’initCreate, j’active la possibilité d’update de certains champs.
Lors du preValidate, je reset la propriété required de tous mes champs, puis j’en mets certains à required selon le type de la demande.
La solution trouvée consiste à mettre

	@Override
	public String postCreate() {
	 	for (ObjectField objectField : this.getFields()) {
	 		objectField.setRequired(objectField.getRequiredDefault());
	 	}
	 	return null;
	}

En soit, pourquoi pas car si on laisse la définition altérée certains éléments anciennement créés ne seront plus corrects. A partir de là, plusieurs questions :

  • mon objet nouvellement créé a passé toutes les étapes de validations donc un get de cet objet est normalement conforme à la définition modifiée ? (Quel est le hook appelé juste après le create ? un get ?)
  • côté back, puisque la définition altérée était toujours en place, est ce que je n’aurais pas du voir uniquement les records qui validaient cette définition ?
  • et pourquoi la sauvegarde fonctionnait côté back mais pas front ?

Si ce qui se passe dans une session privée a un impact sur les instances d’objet utilisé par le user public. c’est que vous êtes au sein d’une même session navigateur (ex: plusieurs onglets)

Dans un cas de figure comme celui-ci tout se mélange entre la session privée et publique avec des effets de bord incompréhensibles.

Il faut toujours travailler la partie publique dans une fenêtre de nav privée ou dans un autre navigateur pour être certain d’avoir des sessions Tomcat distinctes.

Plus généralement le minimum à faire c’est au moins de ne pas utiliser les mêmes instances d’objet (ex: dans le code “public” utilisez une instance préfixée en public_ )

@Amandine ce qu’il y a dans votre code est typiquement ce que je disais précédement => vous jouez sur le caractère obligatoire/facultatif des attributs, or s’il y a des FK parmi ces attributs vous tomberez dans le pb du inner/outer join (cf. le post cité) qui rend potentiellement des records inaccessibles de manière “incompréhensible”.

Jouer sur le caractère obligatoire/facultatif des FK est toujours très subtil, il faut vraiment être à 100% sûr de ce qu’on fait car on tombe rapidement dans des truc inextricables. Perso je ne le fais jamais, au pire je met des règles de vérification de la FK dans les cas particuliers où elle est requise (dans le postValidate) mais en laissant toujours ma FK facultative.

Ca ne remet pas en cause ce que je dis sur les user public vs le user technique ni ce que je dis sur le partage de session Tomcat dans une session navigateur qui est une autre source de comportement incompréhensibles.