Jooq- Updating a field to NULL makes it "null" and not NULL - jooq

So this was my query that previously worked in jooq 3.11.
I am updating a JSON field, however it is mapped to a String in my model using a JsonBinding which I will post down below
dsl.update(TASK)
.set(TASK.JSON_SOLUTION, (String) null).
.where(TASK.TENANT.eq(getCurrentTenant()))
.and(TASK.TASK_TEMPLATE_ID.in(taskTemplateIds));execute()
This now no longer works after upgrading to jooq 3.13.2. I also had to change my sql dialect to be mysql even though I am working with a mysql 5_7 database, this may be the issue?
I have also tried this and it is still the same
dsl.update(TASK)
.setNull(TASK.JSON_SOLUTION).
.where(TASK.TENANT.eq(getCurrentTenant()))
.and(TASK.TASK_TEMPLATE_ID.in(taskTemplateIds));execute()
JsonBinding.class
public class JsonBinding implements Binding<JSON, String> {
#Override
public Converter<JSON, String> converter() {
return new JsonConverter();
}
#Override
public void sql(BindingSQLContext<String> bindingSQLContext) {
if (bindingSQLContext.render().paramType() == ParamType.INLINED) {
bindingSQLContext
.render()
.visit(DSL.inline(bindingSQLContext.convert(converter()).value()))
.sql("::json");
} else {
bindingSQLContext.render().sql("?");
}
}
#Override
public void register(BindingRegisterContext<String> bindingRegisterContext) throws SQLException {
bindingRegisterContext
.statement()
.registerOutParameter(bindingRegisterContext.index(), Types.VARCHAR);
}
#Override
public void set(BindingSetStatementContext<String> bindingSetStatementContext)
throws SQLException {
bindingSetStatementContext
.statement()
.setString(
bindingSetStatementContext.index(),
Objects.toString(bindingSetStatementContext.convert(converter()).value(), null));
}
#Override
public void set(BindingSetSQLOutputContext<String> bindingSetSQLOutputContext)
throws SQLException {
throw new SQLFeatureNotSupportedException();
}
#Override
public void get(BindingGetResultSetContext<String> bindingGetResultSetContext)
throws SQLException {
bindingGetResultSetContext
.convert(converter())
.value(
JSON.valueOf(
bindingGetResultSetContext
.resultSet()
.getString(bindingGetResultSetContext.index())));
}
#Override
public void get(BindingGetStatementContext<String> bindingGetStatementContext)
throws SQLException {
bindingGetStatementContext
.convert(converter())
.value(
JSON.valueOf(
bindingGetStatementContext
.statement()
.getString(bindingGetStatementContext.index())));
}
#Override
public void get(BindingGetSQLInputContext<String> bindingGetSQLInputContext) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
}
JsonConverter.class
public class JsonConverter implements Converter<JSON, String> {
#Override
public String from(JSON object) {
return object != null ? object.toString() : null;
}
#Override
public JSON to(String string) {
return JSON.valueOf(string);
}
#Override
public Class<JSON> fromType() {
return JSON.class;
}
#Override
public Class<String> toType() {
return String.class;
}
}
Here is the query jooq runs with .setNull()
update `tasks_service`.`task` set `tasks_service`.`task`.`json_solution` = 'null'::json where (`tasks_service`.`task`.`tenant` = 'skynet' and `tasks_service`.`task`.`task_template_id` in ('55', '33'))
Before the upgrade on jooq 3.11 the query comes out as this
update `tasks_service`.`task` set `tasks_service`.`task`.`json_solution` = null::json where (`tasks_service`.`task`.`tenant` = 'skynet' and `tasks_service`.`task`.`task_template_id` in ('55', '33'))
So before its set 'json_solution' = null and after the upgrade it seems to be set 'json_solution' = 'null'
Not quite sure why this is occurring?
Edit: So from what I can tell this solely seems to be from the upgrade in JOOQ and not the sql-dialect. Using Jooq 3.11.5 with both mysql and mysql_5_7 as the dialects, the query is built as set 'json_solution' = null, if I upgrade JOOQ to 3.13.2 its set 'json_solution' = 'null'
This quirk also seems to only happen on the JSON field, I tried setting another varchar String field to be null on the same table, and I get the correct set "field_name' = null
The problem may be with my JsonBinding/JsonConverter ? I had to modify it slightly to work with the new JSON object in JOOQ, as previously JOOQ mapped JSON as Object

JSON.valueOf(null) vs (JSON) null
The answer is in the Javadoc of org.jooq.JSON:
A CAST(NULL AS JSON) value is represented by a null reference of type JSON, not as data() == null. This is consistent with jOOQ's general way of returning NULL from Result and Record methods.
So, the mistake is in JsonConverter's usage of JSON.valueOf(). Write this instead:
public JSON to(String string) {
return string == null ? null : JSON.valueOf(string);
}
Or, just use Converter.ofNullable(), which handles the null-to-null mapping for you:
Converter<JSON, String> converter = Converter.ofNullable(
JSON.class,
String.class,
JSON::data,
JSON::json
);
Side note on using a Binding
You don't really need the binding anymore, now that the JSON type is suppported natively by jOOQ. If you want to convert JSON to String, your Converter will be sufficient.

Related

PrimeFaces primeDateRangeValidator - Get variable

I need help with a problem i have using the primeDateRangeValidator.I need to get a variable (private boolean validacionFechas;) from this class and use it in another one. (Sorry for my bad english).
#FacesValidator("primeDateRangeValidator")
public class PrimeDateRangeValidator implements Validator {
private boolean validacionFechas;
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (value == null) {
return;
}
//Leave the null handling of startDate to required="true"
Object startDateValue = component.getAttributes().get("fi");
if (startDateValue==null) {
return;
}
Date startDate = (Date)startDateValue;
Date endDate = (Date)value;
if (endDate.before(startDate)) {
this.validacionFechas = false;
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "¡Error!", "La fecha inicial no puede ser mayor a la final."));
}
}
public boolean getValidacionFechas() {
return validacionFechas;
}
public void setValidacionFechas(boolean validacionFechas) {
this.validacionFechas = validacionFechas;
}
}
The validation between the dates are correct, and the message too. But from another class(where i have my Save method) i'm calling the variable this way:
PrimeDateRangeValidator pdrv = new PrimeDateRangeValidator();
pdrv.getValidacionFechas();
And i'm always getting TRUE, cousing this to save the information if the dates are correct, and when the dates are incorrect show the error message but saves the information too.
Is there any problem with the #FacesValidator or with the set and get?
Your implementation can't work. You are creating a new instance of PrimeDateRangeValidator which has nothing to do with the
validator instance that JSF creates and uses itself. I'm just wondering why getValidacionFechas always returns true. It should
return false.
I think the best way is to put the information of the validation into the session map, for example:
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("VALIDACION_FECHAS", false);
And read it like that:
boolean validacionFechas = (boolean) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("VALIDACION_FECHAS");

AdviceAdapter onMethodExit never called

I'm attempting to instrument a cassandra driver and in particular need to modify a ResultSet class to hang on to some information. In order to do this I need to modify the code where the instance is being allocated, which is a static method in another class. The code has this snippet in it:
return r.metadata.pagingState == null
? new SinglePage(columnDefs, tokenFactory, protocolVersion, columnDefs.codecRegistry, r.data, info)
: new MultiPage(columnDefs, tokenFactory, protocolVersion, columnDefs.codecRegistry, r.data, info, r.metadata.pagingState, session);
It also has other returns within the method. So my thought was to use an AdviceAdapter on this method and use the onMethodExit(). However, my method was never called. That seems absurd since.. the method has to be returning! After a little debugging, I find the visitInsn() in the AdviceAdapter class is being called just once, with an opcode of IALOAD (load an int from an array?).
I guess my question is.. what the hell is going on? Heh.. sorry, bonked my head on my desk a few too many times today.
EDIT: I changed my class to be a simple MethodVisitor just to see if could see more opcodes, and indeed I do! I see it all! I just no longer have access to dup(). :(
I have used an EmailAdviceAdaptor (for javax/mail/Transport) in one of my project, Code is given below. Hope this code will help to resolve your issue.
package com.mail.agent.adapter;
import com.mail.jtm.BTMConstants;
import com.mail.org.objectweb.asm.Label;
import com.mail.org.objectweb.asm.MethodVisitor;
import com.mail.org.objectweb.asm.Opcodes;
import com.mail.org.objectweb.asm.Type;
import com.mail.org.objectweb.asm.commons.AdviceAdapter;
public class MyEmailAdviceAdapter extends AdviceAdapter {
private String methodName;
private String className;
private String description;
private static final String MAIL_SEND_METHOD1_DESC="(Ljavax/mail/Message;)V";
private static final String MAIL_SEND_METHOD2_DESC="(Ljavax/mail/Message;[Ljavax/mail/Address;)V";
private static final String MAIL_SENDMESSAGE_METHOD_DESC="(Ljavax/mail/Message;[Ljavax/mail/Address;)V";
private boolean isSendMethod;
private int okFlag = newLocal(Type.BOOLEAN_TYPE);
Label startFinally = new Label();
public MyEmailAdviceAdapter(int access , MethodVisitor mv , String methodName, String description, String className, int classFileVersion){
super(Opcodes.ASM5 , mv, access, methodName, description);
this.className = className;
this.methodName = methodName;
this.description = description;
this.isSendMethod = false;
if(methodName.equals("send")){
if( description.equals(MAIL_SEND_METHOD1_DESC) || description.equals(MAIL_SEND_METHOD2_DESC)){
isSendMethod = true;
}
}
else if(methodName.equals("sendMessage") && description.equals(MAIL_SENDMESSAGE_METHOD_DESC)){
isSendMethod = true;
}
}
public void visitCode() {
super.visitCode();
mv.visitLabel(startFinally);
}
protected void onMethodEnter(){
if(isSendMethod) {
mv.visitInsn(Opcodes.ICONST_0);
mv.visitVarInsn(ISTORE, okFlag);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "mailMethodBegin", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z"; , false);
mv.visitVarInsn(ISTORE, okFlag);
}
}
protected void onMethodExit(int opcode){
if(opcode!=ATHROW) {
onFinally(opcode);
}
}
public void visitMaxs(int maxStack, int maxLocals){
Label endFinally = new Label();
mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null);
mv.visitLabel(endFinally);
onFinally(ATHROW);
mv.visitInsn(ATHROW);
mv.visitMaxs(maxStack, maxLocals);
}
private void onFinally(int opcode){
if(isSendMethod){
// If the method throws any exception
if(opcode == ATHROW){
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitVarInsn(ILOAD, okFlag);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "recordException", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V", false);
}
mv.visitLdcInsn(className);
mv.visitLdcInsn(methodName);
mv.visitLdcInsn(description);
mv.visitVarInsn(ILOAD, okFlag);
mv.visitLdcInsn(opcode);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/mail/agent/trace/MailTracer", "mailMethodEnd", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZI)V", false);
}
}
}

VaryByParam fails if a param is a list

I've got this action in MVC
[OutputCache(Duration = 1200, VaryByParam = "*")]
public ActionResult FilterArea( string listType, List<int> designersID, int currPage = 1 )
{
// Code removed
}
that fails to present the correct HTML with url like
http://example.com/en-US/women/clothing?designersID=158
http://example.com/en-US/women/clothing?designersID=158&designersID=13
Is this a know bug of OutputCache in .NET cause cannot recognize VaryByParam with a list param or am I missing something?
I too had the same issue in MVC3 and I believe it's still the same case in MVC5.
Here is the setup I had.
Request
POST, Content-Type:application/json, passing in an array of string as the parameter
{ "options": ["option1", "option2"] }
Controller Method
[OutputCache(Duration = 3600, Location = OutputCacheLocation.Any, VaryByParam = "options")]
public ActionResult GetOptionValues(List<string> options)
I tried every option possible with OutputCache and it just wasn't caching for me. Binding worked fine for the actual method to work. My biggest suspicion was that OutputCache wasn't creating unique cache keys so I even pulled its code out of System.Web.MVC.OutputCache to verify. I've verified that it properly builds unique keys even when a List<string> is passed in. Something else is buggy in there but wasn't worth spending more effort.
OutputCacheAttribute.GetUniqueIdFromActionParameters(filterContext,
OutputCacheAttribute.SplitVaryByParam(this.VaryByParam);
Workaround
I ended up creating my own OutputCache attribute following another SO post. Much easier to use and I can go enjoy the rest of the day.
Controller Method
[MyOutputCache(Duration=3600)]
public ActionResult GetOptionValues(Options options)
Custom Request class
I've inherited from List<string> so I can call the overriden .ToString() method in MyOutputcache class to give me a unique cache key string. This approach alone has resolved similar issues for others but not for me.
[DataContract(Name = "Options", Namespace = "")]
public class Options: List<string>
{
public override string ToString()
{
var optionsString= new StringBuilder();
foreach (var option in this)
{
optionsString.Append(option);
}
return optionsString.ToString();
}
}
Custom OutputCache class
public class MyOutputCache : ActionFilterAttribute
{
private string _cachedKey;
public int Duration { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.Url != null)
{
var path = filterContext.HttpContext.Request.Url.PathAndQuery;
var attributeNames = filterContext.ActionParameters["Options"] as AttributeNames;
if (attributeNames != null) _cachedKey = "MYOUTPUTCACHE:" + path + attributeNames;
}
if (filterContext.HttpContext.Cache[_cachedKey] != null)
{
filterContext.Result = (ActionResult) filterContext.HttpContext.Cache[_cachedKey];
}
else
{
base.OnActionExecuting(filterContext);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Cache.Add(_cachedKey, filterContext.Result, null,
DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Default, null);
base.OnActionExecuted(filterContext);
}
}

How do I prevent duplicate entries using the UnitOfWork pattern with code first Entity Framework?

I am using the Unit of Work and Generic Repository pattern. Here is the statement that checks for a duplicate entry:
int id = int.Parse(beer.id); //id comes from the item we're hoping to insert
if (_unitOfWork.BeerRepository.GetByID(id) == null)
\\create a new model br
_unitOfWork.BeerRepository.Insert(br);
_unitOfWork.save();
Apparently this is failing to check to see if the beer is already in the database because I get this inner exception:
Violation of PRIMARY KEY constraint 'PK_Beers_3214EC2703317E3D'.
Cannot insert duplicate key in object 'dbo.Beers'.\r\nThe statement
has been terminated.
I also get this message:
An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details.
The UnitOfWork class has my BeerRecommenderContext which implements DbContext and the UnitOfWork has a generic repository for each entity:
namespace BeerRecommender.Models
{
public class GenericRepository<TEntity> where TEntity : class
{
internal BeerRecommenderContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(BeerRecommenderContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
}
I have a similar usage of repository using code-first. Occasionally, I would see conflicts like the one you described. My issue was with change tracking across multiple processes. Are you inserting items into the database inside one process (using a single entity context)?
If you are, you should look at the Merge Options available with Entity Framework. If you are using the default merge option (AppendOnly), then you could be querying the in memory context instead of going to the database. This could cause the behaviour you are describing.
Unfortunately, as far as I understand, all the merge options are not yet exposed to Code-First. You can choose the default (AppendOnly) or NoTracking, which will go to the database every time.
Hope this helps,
Davin

How to pass parameters to a CodeActivity in a NativeActivity code sequence

I'm trying to get windows workflows working, and I've become a little stumped.
I've gotten a single workflow working, but now I am trying to do something a little more complex: start a workflow, where each activity itself contains a workflow. (Picture something like the main program starts the activities "Input, logic, and output", and then each of those have additional activities like "prompt user, get input, etc.")
I've had it working fine, with the example from here (http://msdn.microsoft.com/en-us/magazine/gg535667.aspx), when I am not passing any parameters from the main program to the activites. My question is, how exactly does the 'Variables' and 'metadata.SetVariablesCollection' work in the NativeActivity, and how to I get the parameters to the low level activities?
This is what I am currently trying:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Collections.ObjectModel;
using System.Activities.Statements;
namespace Project1
{
internal class MainProgram
{
internal static void Main(string[] args)
{
try
{
var act = new SimpleSequence();
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Variables.Add(new Variable<string> ("stringArg", "TEXT"));
WorkflowInvoker.Invoke(act);
}
catch (Exception ex)
{
System.Console.WriteLine("EXCEPTION: {0}", ex);
}
}
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
protected override void Execute(CodeActivityContext context)
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
public class SimpleSequence : NativeActivity
{
Collection<Activity> activities;
Collection<Variable> variables;
Variable<int> current = new Variable<int> { Default = 0 };
public Collection<Activity> Activities
{
get
{
if (this.activities == null)
this.activities = new Collection<Activity>();
return this.activities;
}
set
{
this.activities = value;
}
}
public Collection<Variable> Variables
{
get
{
if (this.variables == null)
this.variables = new Collection<Variable>();
return this.variables;
}
set
{
this.variables = value;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.SetChildrenCollection(this.activities);
metadata.SetVariablesCollection(this.variables);
metadata.AddImplementationVariable(this.current);
}
protected override void Execute(NativeActivityContext context)
{
if (this.Activities.Count > 0)
context.ScheduleActivity(this.Activities[0], onChildComplete);
}
void onChildComplete(NativeActivityContext context, ActivityInstance completed)
{
int currentExecutingActivity = this.current.Get(context);
int next = currentExecutingActivity + 1;
if (next < this.Activities.Count)
{
context.ScheduleActivity(this.Activities[next], this.onChildComplete);
this.current.Set(context, next);
}
}
}
}
}
This ends up throwing the following exception:
EXCEPTION: System.Activities.InvalidWorkflowException: The following errors were encountered while processing the workflow tree:
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
at System.Activities.Validation.ActivityValidationServices.ThrowIfViolationsExist(IList`1 validationErrors)
at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at System.Activities.WorkflowApplication.RunInstance(WorkflowApplication instance)
at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
at Project1.MainProgram.Main(String[] args) in c:\users\user\documents\visual studio 2010\Projects\ModelingProject1\Project1\MainProgram.cs:line 25
I know, I only pass 1 parameter, but the exception still says that I am missing 3 parameters. I am missing something as to how to do this properly.
You're correctly declaring stringArg as an InArgument but you're not passing any value to it when calling it inside SimpleSequence.
You can pass something using the constructor, while constructing the all activity itself, like this:
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
public WriteSomeText(string stringArg)
{
this.stringArg = stringArg;
}
protected override void Execute(CodeActivityContext context
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
// Calling the activity like this:
internal static void Main(string[] args)
{
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
}
};
WorkflowInvoker.Invoke(act);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
Also notice that is a best practice to use the constructor to initialize collections:
public SimpleSequence()
{
activities = new Collection<Activity>();
variables = new Collection<Variable>();
}
This way is even more intuitive to initialize the activity:
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
},
Variables =
{
new Variable<int>("myNewIntVar", 10),
// ....
}
};
EDIT:
There are a couple of other ways to approach the problem. This is your best friend while starting in the WF4 world.
Check WF\Basic\CustomActivities\Code-Bodied for a little push with this particular case.

Resources