Urgent Help: After Upgrading Jira 7.0.1 to 9.1.1 groovy script stops working and breaks with "Null" values
I have this groovy in a post fuction and it stopped working after migrating jira server 7.0.1 to Jira Server 9.1.1:
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
//import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.issue.comments.ComponentAccessor
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.user.util.UserManager
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.search.SearchProvider;
import com.atlassian.jira.jql.parser.JqlQueryParser;
import com.atlassian.jira.web.bean.PagerFilter;
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";
String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo
// Nueva version
def int Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def int Remito_to_value = (issue.get("Remito To")); // Cota Superior
def int Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def int Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def int Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));
// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info("Valores de Entrada:")
log.info("Get Remito_from_value:" +Remito_from_value);
log.info("Get Remito_usado_value:" +Remito_usado_value);
log.info("Get Remito_to_value:" +Remito_to_value);
log.info("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info("");
log.info("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info("Error:No hay remitos disponibles,cota superior superada");
}
if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info("Remito Asignado A:" +Remito_id_value);
log.info("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);
Error:
There was an error during the execution of your script against issue CRM-336
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'null' with class 'null' to class 'int'. Try 'java.lang.Integer' instead
org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToNumber(DefaultTypeTransformation.java:175) org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.intUnbox(DefaultTypeTransformation.java:81) script_f4e3ffc3a1093c31ab9473ca1f131c62.run(script_f4e3ffc3a1093c31ab9473ca1
f131c62.groovy:31)
I think I have an error in the "import".
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Diego Cañete , is it script for Script Runner console? I can't believe that your script worked in earlier version because for example method
issue.get("XXX)
is not valid method for https://docs.atlassian.com/software/jira/docs/api/7.1.2/com/atlassian/jira/issue/Issue.html
When you upgraded from v7 to v9 you can have problem with version of groovy and version of Java installed on your environment. Can you provide more information, please?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Martin,
Thank you for reply me.
This script is used to take the number of initial and final remittances and take the number of remittances used.
The parent ticket in this case is what we call Checkbook and the related subtasks are the Remittances, the remittances can be created as many as the number of remittances_to. This script controls that no other remittances are created in each Checkbook.
I am going to add an image of a Remittance and a Checkbook.
This before the update worked perfectly, you can see in the images that in the Talonarion recent remittances created appear.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Last try and somes updates in script:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.opensymphony.workflow.InvalidInputException;
import java.sql.ResultSet;
import java.util.Map;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.WorkflowException;
//import groovy.sql.GroovyRowResult
//import groovy.sql.*
//import groovy.sql.Sql
//import java.sql.*;
//import java.sql.Connection;
//import java.sql.ResultSet;
//import java.sql.SQLException;
//import java.sql.Statement;
import org.apache.log4j.Logger;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";
String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo
// Nueva version
def Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def Remito_to_value = (issue.get("Remito To")); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));
// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info ("Valores de Entrada:")
log.info ("Get Remito_from_value:" +Remito_from_value);
log.info ("Get Remito_usado_value:" +Remito_usado_value);
log.info ("Get Remito_to_value:" +Remito_to_value);
log.info ("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info ("");
log.info ("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info ("Error:No hay remitos disponibles,cota superior superada");
}
if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info ("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info ("Remito Asignado A:" +Remito_id_value);
log.info ("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info ("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);
Result:
There was an error during the execution of your script against issue CRM-147
Message:
java.lang.NullPointerException: Cannot invoke method mi
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Full result log:
There was an error during the execution of your script against issue CRM-147
java.lang.NullPointerException: Cannot invoke method minus() on null object
org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:91) org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:43) org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47) org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:34) org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47) org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115) org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:127) script_7468e350f9891d1acdd5321511dc63d8.run(script_7468e350f9891d1acdd5321511dc63d8.groovy:93)
INFO : Valores de Entrada: INFO : Get Remito_from_value:null INFO : Get Remito_usado_value:null INFO : Get Remito_to_value:null INFO : Get Remito_disponibles:0 INFO : INFO : Valores de Salida: INFO : Usted alcanzo el final INFO : Remito Asignado A:null INFO : Estado Activo SI/NO: NO
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
It should say "Talonario" where it says the text that goes up as "CheckBox".
"Talonarios" are a set of "Remitos" that are delivered for the transportation of merchandise here in Argentina..
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Defined how:
Parent: Talonario.
SubTask: Remito
The number of subtasks (Remitos) that can be created in a "Talonario" is defined by the "Remito From" and "Remito To" fields.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Diego Cañete I'm trying to read it and I understand it better (not perfectly :)). Can you adjust your script (only some parts)?
// Nueva version
def Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def Remito_to_value = (issue.get("Remito To")); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));
TO
// Nueva version
def Remito_from_value = (issue.get("Remito From", 0)); // Cota Inferior
def Remito_to_value = (issue.get("Remito To", 0)); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero", 0)); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R", 0)); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));
AND
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);
TO
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);
log.info("Current issue is:" + issue.getKey());
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);
It should be enough to share your logs after you check it...
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Martin, post update the script with the updates sent by you, the log change , it does not pass values but it does not give so many errors.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.opensymphony.workflow.InvalidInputException;
import java.sql.ResultSet;
import java.util.Map;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.WorkflowException;
//import groovy.sql.GroovyRowResult
//import groovy.sql.*
//import groovy.sql.Sql
//import java.sql.*;
//import java.sql.Connection;
//import java.sql.ResultSet;
//import java.sql.SQLException;
//import java.sql.Statement;
import org.apache.log4j.Logger;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";
String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo
// Nueva version
def Remito_from_value = (issue.get("Remito From", 0)); // Cota Inferior
def Remito_to_value = (issue.get("Remito To", 0)); // Cota Superior
def Remito_id_value = (issue.get("Remito Numero", 0)); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def Remito_usado_value = (issue.get("Remito_Utilizado_R", 0)); //Ultimo Remito Calculado
def Remitos_disponibles_value = (issue.get("customfield_19018", 0)); // (issue.get("Available Quantity"));
// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info ("Valores de Entrada:")
log.info ("Get Remito_from_value:" +Remito_from_value);
log.info ("Get Remito_usado_value:" +Remito_usado_value);
log.info ("Get Remito_to_value:" +Remito_to_value);
log.info ("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info ("");
log.info ("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info ("Error:No hay remitos disponibles,cota superior superada");
}
if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info ("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info ("Remito Asignado A:" +Remito_id_value);
log.info ("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);
log.info("Current issue is:" + issue.getKey());
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Your script ran successfully against issue CRM-336
null
INFO : Valores de Entrada: INFO : Get Remito_from_value:0 INFO : Get Remito_usado_value:0 INFO : Get Remito_to_value:0 INFO : Get Remito_disponibles:84.0 INFO : INFO : Valores de Salida: INFO : Usted alcanzo el final INFO : Remito Asignado A:0 INFO : Estado Activo SI/NO: NO INFO : Current issue is:CRM-336 INFO : Available Quantity:1
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Ok, so it looks like the required values for "remito to", "remito from" and "remito usado" is not available on CRM-336. I can't see the values on your screenshots...
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Martin,
You have right,
The last code works but I add "int".
End Code:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Logger
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.opensymphony.workflow.InvalidInputException;
import java.sql.ResultSet;
import java.util.Map;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.workflow.function.issue.AbstractJiraFunctionProvider;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.WorkflowException;
//import groovy.sql.GroovyRowResult
//import groovy.sql.*
//import groovy.sql.Sql
//import java.sql.*;
//import java.sql.Connection;
//import java.sql.ResultSet;
//import java.sql.SQLException;
//import java.sql.Statement;
import org.apache.log4j.Logger;
import org.ofbiz.core.entity.ConnectionFactory;
import org.ofbiz.core.entity.GenericEntityException;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
String Remito_from = "";
String Remito_to = "";
String Control_id = "";
String Remito_id = "";
String Talonario_activo = "";
String Remito_from_name = "customfield_19013"; // Remito From
String Remito_to_name = "customfield_19012"; // Remito to
String Control_id_name = "customfield_19010";
String Remito_id_name = "customfield_13418";
String Remitos_disponibles_name = "customfield_19018"; //Available Quantity
String Talonario_activo_name = "customfield_19011" //Talonario Activo SI/NO
// Nueva version
def int Remito_from_value = (issue.get("Remito From")); // Cota Inferior
def int Remito_to_value = (issue.get("Remito To")); // Cota Superior
def int Remito_id_value = (issue.get("Remito Numero")); // Nuevo numero remito a ser utilizado(Proximo), Teoricamente
def int Remito_usado_value = (issue.get("Remito_Utilizado_R")); //Ultimo Remito Calculado
def int Remitos_disponibles_value = (issue.get("customfield_19018")); // (issue.get("Available Quantity"));
// CASO 1: testear remito_usado < from. Resultado Asignado valor "Remito From"
// Remito_from_value = 10000;
// Remito_usado_value = 88887;
// CASO 2: remito_usado = from
// Remito_usado_value = 1; //Testear Remito usado <1
log.info ("Valores de Entrada:")
log.info ("Get Remito_from_value:" +Remito_from_value);
log.info ("Get Remito_usado_value:" +Remito_usado_value);
log.info ("Get Remito_to_value:" +Remito_to_value);
log.info ("Get Remito_disponibles:" +Remitos_disponibles_value);
// Logica desde aca
if ((Remito_usado_value < Remito_from_value)||(Remito_from_value == 0)) //- Caso 00*/)
{
Remito_id_value = Remito_from_value;
}
else if (Remito_usado_value < Remito_to_value)
{
Remito_id_value = Remito_usado_value + 1;
}
log.info ("");
log.info ("Valores de Salida:");
if (Remito_id_value > Remito_to_value || Remito_usado_value > Remito_to_value)
{
Remito_id_value = Remito_to_value;
log.info ("Error:No hay remitos disponibles,cota superior superada");
}
if (Remito_id_value == Remito_to_value || Remito_usado_value == Remito_to_value) //or( || )
{
log.info ("Usted alcanzo el final");
issue.setFieldValue(Talonario_activo_name, 'NO');
}
log.info ("Remito Asignado A:" +Remito_id_value);
log.info ("Estado Activo SI/NO: " +issue.get("customfield_19011"));
//Linea que llena el numero de remito psas a llenado manual por la pantalla de creacion -- ACANETE --
// issue.setFieldValue("Remito Numero", Remito_id_value);
//-----------------------------------------------------------------------------------------ACANETE--
issue.setFieldValue("Remito_Utilizado_R", Remito_id_value);
log.info("Current issue is:" + issue.getKey());
Remitos_disponibles = (Remito_to_value - Remito_id_value) +1;
log.info("Available Quantity:" +Remitos_disponibles);
issue.setFieldValue(Remitos_disponibles_name, Remitos_disponibles);
For check, if I put values pre assigned the log result is:
Your script ran successfully against issue CRM-336
null
INFO : Valores de Entrada: INFO : Get Remito_from_value:501 INFO : Get Remito_usado_value:519 INFO : Get Remito_to_value:600 INFO : Get Remito_disponibles:81 INFO : INFO : Valores de Salida: INFO : Remito Asignado A:520 INFO : Estado Activo SI/NO: SI INFO : Current issue is:CRM-336 INFO : Available Quantity:81
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Martin,
Thank you very much for your assistance with them I managed to make it work and a lot of attitude too.
Thank you so much...
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Martin,
You just have to be very careful in the location of the grooby in the psot function, it must be after create issue and not before, otherwise it doesn't work.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Diego Cañete happy to help :). It would be great if you accept my answer :) thank you
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.