Java openjpa-all 3.2.0 or latest with sqlite getting error to find 'user' column name - sqlite

I have a web application project using sqlite latest version (3.40.1.0) and openjpa-all latest (3.2.2).
I created a dummy db using sqlite3 command with the following query:
CREATE TABLE test (
id INTEGER PRIMARY KEY NOT NULL,
user TEXT COLLATE NOCASE NOT NULL
);
INSERT INTO test VALUES (1,'test');
Using openjpa-all to recover data (select statement), got an error message from openjpa framework saying that USER0 column is not valid. Instead I am using the right column name 'user' and for some reason framework is switching the name of column to USER0.
Any clue how to resolve?
Here is the exception:
Exception in thread "main" <openjpa-3.2.2-re5933d6 fatal general error> org.apache.openjpa.persistence.PersistenceException: [SQLITE_ERROR] SQL error or missing database (no such column: t0.USER0) {SELECT t0.id, t0.USER0 FROM test t0} [code=1, state=null]
FailedObject: SELECT a FROM Test a [java.lang.String]
at org.apache.openjpa.jdbc.sql.DBDictionary.narrow(DBDictionary.java:5326)
at org.apache.openjpa.jdbc.sql.DBDictionary.newStoreException(DBDictionary.java:5286)
at org.apache.openjpa.jdbc.sql.SQLExceptions.getStore(SQLExceptions.java:134)
at org.apache.openjpa.jdbc.sql.SQLExceptions.getStore(SQLExceptions.java:115)
at org.apache.openjpa.jdbc.sql.SQLExceptions.getStore(SQLExceptions.java:67)
at org.apache.openjpa.jdbc.kernel.SelectResultObjectProvider.handleCheckedException(SelectResultObjectProvider.java:162)
at org.apache.openjpa.lib.rop.EagerResultList.<init>(EagerResultList.java:42)
at org.apache.openjpa.kernel.QueryImpl.toResult(QueryImpl.java:1314)
at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:1061)
at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:911)
at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:842)
at org.apache.openjpa.kernel.DelegatingQuery.execute(DelegatingQuery.java:601)
at org.apache.openjpa.persistence.QueryImpl.execute(QueryImpl.java:297)
at org.apache.openjpa.persistence.QueryImpl.getResultList(QueryImpl.java:314)
at com.kyndryl.main.Testando.main(Testando.java:31)
Caused by: org.apache.openjpa.lib.jdbc.ReportingSQLException: [SQLITE_ERROR] SQL error or missing database (no such column: t0.USER0) {SELECT t0.id, t0.USER0 FROM test t0} [code=1, state=null]
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:219)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:199)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.access$000(LoggingConnectionDecorator.java:58)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$LoggingConnection.prepareStatement(LoggingConnectionDecorator.java:252)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:138)
at org.apache.openjpa.lib.jdbc.ConfiguringConnectionDecorator$ConfiguringConnection.prepareStatement(ConfiguringConnectionDecorator.java:144)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:138)
at org.apache.openjpa.jdbc.kernel.JDBCStoreManager$RefCountConnection.prepareStatement(JDBCStoreManager.java:1699)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:127)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:517)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:497)
at org.apache.openjpa.jdbc.sql.SelectImpl.prepareStatement(SelectImpl.java:511)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:452)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:423)
at org.apache.openjpa.jdbc.sql.LogicalUnion$UnionSelect.execute(LogicalUnion.java:477)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:259)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:248)
at org.apache.openjpa.jdbc.kernel.SelectResultObjectProvider.open(SelectResultObjectProvider.java:95)
at org.apache.openjpa.lib.rop.EagerResultList.<init>(EagerResultList.java:36)
... 8 more
NestedThrowables:
org.sqlite.SQLiteException: [SQLITE_ERROR] SQL error or missing database (no such column: t0.USER0)
at org.sqlite.core.DB.newSQLException(DB.java:1179)
at org.sqlite.core.DB.newSQLException(DB.java:1190)
at org.sqlite.core.DB.throwex(DB.java:1150)
at org.sqlite.core.NativeDB.prepare_utf8(Native Method)
at org.sqlite.core.NativeDB.prepare(NativeDB.java:126)
at org.sqlite.core.DB.prepare(DB.java:264)
at org.sqlite.core.CorePreparedStatement.<init>(CorePreparedStatement.java:46)
at org.sqlite.jdbc3.JDBC3PreparedStatement.<init>(JDBC3PreparedStatement.java:31)
at org.sqlite.jdbc4.JDBC4PreparedStatement.<init>(JDBC4PreparedStatement.java:25)
at org.sqlite.jdbc4.JDBC4Connection.prepareStatement(JDBC4Connection.java:34)
at org.sqlite.jdbc3.JDBC3Connection.prepareStatement(JDBC3Connection.java:226)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:140)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$LoggingConnection.prepareStatement(LoggingConnectionDecorator.java:249)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:138)
at org.apache.openjpa.lib.jdbc.ConfiguringConnectionDecorator$ConfiguringConnection.prepareStatement(ConfiguringConnectionDecorator.java:144)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:138)
at org.apache.openjpa.jdbc.kernel.JDBCStoreManager$RefCountConnection.prepareStatement(JDBCStoreManager.java:1699)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:127)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:517)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:497)
at org.apache.openjpa.jdbc.sql.SelectImpl.prepareStatement(SelectImpl.java:511)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:452)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:423)
at org.apache.openjpa.jdbc.sql.LogicalUnion$UnionSelect.execute(LogicalUnion.java:477)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:259)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:248)
at org.apache.openjpa.jdbc.kernel.SelectResultObjectProvider.open(SelectResultObjectProvider.java:95)
at org.apache.openjpa.lib.rop.EagerResultList.<init>(EagerResultList.java:36)
at org.apache.openjpa.kernel.QueryImpl.toResult(QueryImpl.java:1314)
at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:1061)
at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:911)
at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:842)
at org.apache.openjpa.kernel.DelegatingQuery.execute(DelegatingQuery.java:601)
at org.apache.openjpa.persistence.QueryImpl.execute(QueryImpl.java:297)
at org.apache.openjpa.persistence.QueryImpl.getResultList(QueryImpl.java:314)
at com.kyndryl.main.Testando.main(Testando.java:31)
This is the following code I did to recover the data:
package com.company.entities;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "test")
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Basic(optional = false)
#Column(name = "user", nullable = false)
private String name;
public Test() {
super();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Teste [id=" + id + ", name=" + name + "]";
}
}

I found the solution. Much simple then I thought. Add double quotes in the column name. See column annotation of name attribute like below.
#Entity
#Table(name = "test")
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Basic(optional = false)
#Column(name = "\"user\"", nullable = false)
private String name;

Related

Why are my foreign keys not showing up when I view my db on a db browser? Am I entering them wrong?

package com.example.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
//making db and setting tables 1-6 names
private static final int DATABASE_VERSION = 4;
private static final String DATABASE_NAME = "contacts.db";
private static final String TABLE1_NAME = "contacts";
private static final String TABLE2_NAME = "Uinfo";
private static final String TABLE3_NAME = "exercises";
private static final String TABLE4_NAME = "calories";
private static final String TABLE5_NAME = "calendar";
// private static final String TABLE6_NAME = "settings";
//these are for table 1
private static final String COLUMN_ID = "id";
private static final String COLUMN_EMAIL = "email";
private static final String COLUMN_PASS = "pass";
private static final String COLUMN_UNAME = "uname";
private static final String COLUMN_NAME = "name";
//for table 2
private static final String COLUMN_ID2 = "id2";
private static final String COLUMN_AGE = "age";
private static final String COLUMN_WEIGHT = "weight";
private static final String COLUMN_HEIGHT = "height";
private static final String COLUMN_SEX = "sex";
private static final String COLUMN_FK_CONTACTS_ID = "id";
// private static final String COLUMN_FK_SETTINGS_ID = "id_6";
private static final String COLUMN_FK_CALENDAR_ID = "id5";
//creating tables
private static final String TABLE_CREATE = "create table " + TABLE1_NAME +
" (id integer primary key AUTOINCREMENT not null ," +
" name text not null , " +
" email text not null ," +
" pass text not null, " +
" uname text not null);";
private static final String TABLE2_CREATE = "create table " + TABLE2_NAME +
"(id2 integer primary key AUTOINCREMENT not null ," +
" age text not null , " +
" weight text not null ," +
" height text not null, " +
" sex text not null, " +
" FOREIGN KEY ("+ COLUMN_ID2 +") REFERENCES "+TABLE1_NAME+"("+COLUMN_ID+")," +
" FOREIGN KEY ("+ COLUMN_ID2 +") REFERENCES "+TABLE5_NAME+"("+COLUMN_ID5+"));";
//" FOREIGN KEY ("+ COLUMN_ID2 +") REFERENCES "+TABLE6_NAME+"("+COLUMN_ID6+"));";
basically the foreign keys don't show up in the db when viewing it.
I've tried adding in
"id integer not null"
before the first foreign key line and that made the database stop writing data to that table. I've updated the database version to see if that would make a difference too, but to no avail.
Am I calling it wrong? I could just be missing the correct way to write in the column the foreign key goes into. Though I'm not sure how it would be inserted the correct way.
I believe your issue is a misunderstanding of what the FOREIGN KEY .... clause does.
It does not create a column for storing the foreign key. Rather it adds a constraint (a rule) that says that the column(s) in the table being created MUST contain an existing value in the table(columns) specified after REFERENCES.
The statements are correct and actually do create the Foreign Key constraints; as will be shown.
Consider the following (taken by running the code using Android Studio that you have supplied (plus code to make it usable) and thus creating the database and the tables needed)
using DEBUG to stop when creating the Uinfo table (as well as the other tables) and copying the value from the TABLE2_CREATE variable the value obtained is :-
create table Uinfo(id2 integer primary key AUTOINCREMENT not null , age text not null , weight text not null , height text not null, sex text not null, FOREIGN KEY (id2) REFERENCES contacts(id), FOREIGN KEY (id2) REFERENCES calendar(id5));
Dropping this value into an SQLite tool (navicat) and then running the following SQL:-
create table contacts (id integer primary key AUTOINCREMENT not null , name text not null , email text not null , pass text not null, uname text not null);
CREATE TABLE calendar(id5 INTEGER PRIMARY KEY,other TEXT);
create table Uinfo(id2 integer primary key AUTOINCREMENT not null , age text not null , weight text not null , height text not null, sex text not null,
FOREIGN KEY (id2) REFERENCES contacts(id),
FOREIGN KEY (id2) REFERENCES calendar(id5));
SELECT * FROM Uinfo;
INSERT INTO Uinfo VALUES(null,10,5.6,70,'Female');
Results in :-
Clearly the INSERT did not insert the data. A look at the log shows:-
create table Uinfo(id2 integer primary key AUTOINCREMENT not null , age text not null , weight text not null , height text not null, sex text not null,
FOREIGN KEY (id2) REFERENCES contacts(id),
FOREIGN KEY (id2) REFERENCES calendar(id5))
> OK
> Time: 0.074s
SELECT * FROM Uinfo
> OK
> Time: 0s
INSERT INTO Uinfo VALUES(null,10,5.6,70,'Female')
> FOREIGN KEY constraint failed
> Time: 0s
So the foreign key constraint has been added.
Doing the equivalent in the testing Android App was undertaken using the code:-
public class MainActivity extends AppCompatActivity {
DatabaseHelper DBHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DBHelper = new DatabaseHelper(this);
SQLiteDatabase db = DBHelper.getWritableDatabase();
db.execSQL("INSERT INTO Uinfo VALUES(null,10,5.6,70,'Female')");
Log.d("DBINFO","After the first insert");
db.execSQL("PRAGMA foreign_keys = true");
db.execSQL("INSERT INTO Uinfo VALUES(null,10,5.6,70,'Female')");
Log.d("DBINFO","After the second insert");
}
}
results in :-
2021-04-24 15:34:27.954 5644-5644/a.a.so67224090javafknotshowing D/DBINFO: After the first insert
2021-04-24 15:34:27.955 5644-5644/a.a.so67224090javafknotshowing D/AndroidRuntime: Shutting down VM
2021-04-24 15:34:27.957 5644-5644/a.a.so67224090javafknotshowing E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so67224090javafknotshowing, PID: 5644
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so67224090javafknotshowing/a.a.so67224090javafknotshowing.MainActivity}: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:748)
at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1770)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1698)
at a.a.so67224090javafknotshowing.MainActivity.onCreate(MainActivity.java:20)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loop(Looper.java:193) 
at android.app.ActivityThread.main(ActivityThread.java:6669) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
The App has crashed with an exception, the exception message being FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
BUT the log also contains D/DBINFO: After the first insert.
i.e. the first INSERT did not cause the exception, it was the second after THE IMPORTANT db.execSQL("PRAGMA foreign_keys = true");
Without this line (or an equivalent) then the default on Android (as it is with the default SQLite) is that Foreign Key processing is turned off. The above turns Foreign Key processing on.
So if you issue is more that the foreign keys do nothing then this could be the cause.

How to automatically set timestamp in room SQLite database?

I am trying to have SQLite create automatic timestamps with CURRENT_TIMESTAMP.
I took the liberty of using Google's code:
// roomVersion = '2.2.2'
#Entity
public class Playlist {
#PrimaryKey(autoGenerate = true)
long playlistId;
String name;
#Nullable
String description;
#ColumnInfo(defaultValue = "normal")
String category;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String createdTime;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String lastModifiedTime;
}
#Dao
interface PlaylistDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(playlist: Playlist): Long
}
This translates into an SQLite-Statement:
CREATE TABLE `Playlist` (
`playlistId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` TEXT,
`description` TEXT,
`category` TEXT DEFAULT 'normal',
`createdTime` TEXT DEFAULT CURRENT_TIMESTAMP,
`lastModifiedTime` TEXT DEFAULT CURRENT_TIMESTAMP
)
I did make one insert:
mDb.playListDao().insert(Playlist().apply { name = "Test 1" })
But the timestamps are always Null.
With the DB Browser for SQLite I added another entry, here I get timestamps.
How do I insert without a Null-Timestamp in room?
(Info: createdTime is also always the same as lastModifiedTime. I think this has to be done with triggers in SQLite, but that is a different problem not to be discussed here).
You don't need to use another class, you can use #Query as an alternative to the convenience #Insert.
as per :-
There are 4 type of statements supported in Query methods: SELECT, INSERT, UPDATE, and DELETE.
Query
e.g.
#Query("INSERT INTO test_table001 (name) VALUES(:name) ")
void insert(String name);
You are also not limited to CURRENT_TIMESTAMP as the only means of getting the current timestamp you can use embedded datetime functions (as is shown below), which can store the value more efficiently and also be more flexible e.g. you could adjust the current time using modifiers such as '+7 days'.
If you consider the following :-
#Entity(tableName = "test_table001")
public class TestTable001 {
#PrimaryKey
Long id;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String dt1;
#ColumnInfo(defaultValue = "(datetime('now'))")
String dt2;
#ColumnInfo(defaultValue = "(strftime('%s','now'))")
String dt3;
String name;
}
Note that the inefficient autogenerate = true has not been used BUT as will be shown you can still have an SQLite assigned id (note that you must use the type Long/Integer as opposed to long or int)
Also note the alternative ways of getting the current date time (the latter being more efficient as the value will ultimately be stored as an Integer (max 8 bytes) rather than a more byte hungry String).
With a Dao as :-
#Dao
public interface TestTable001Dao {
#Insert()
long insert(TestTable001 testTable001);
#Query("INSERT INTO test_table001 (name) VALUES(:name) ")
long insert(String name);
#Query("SELECT * FROM test_table001")
List<TestTable001> getAllTestTable001();
}
And the following to test/demonstrate :-
public class MainActivity extends AppCompatActivity {
AppDatabase mRoomDB;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRoomDB = Room.databaseBuilder(this,AppDatabase.class,"testdb")
.allowMainThreadQueries()
.build();
TestTable001 tt01 = new TestTable001();
tt01.setName("tt01");
mRoomDB.useTestTable001().insert(tt01);
mRoomDB.useTestTable001().insert("tt02");
logAllTestTable001();
}
private void logAllTestTable001() {
for (TestTable001 tt: mRoomDB.useTestTable001().getAllTestTable001()) {
Log.d(
"TTINFO",
"ID = " + tt.getId() +
" Name = " + tt.getName() +
" Date1 = " + tt.getDt1() +
" Date2 = " + tt.getDt2() +
" Date3 = " + tt.getDt3());
}
}
}
The result is :-
2019-12-14 03:18:32.569 D/TTINFO: ID = 1 Name = tt01 Date1 = null Date2 = null Date3 = null
2019-12-14 03:18:32.569 D/TTINFO: ID = 2 Name = tt02 Date1 = 2019-12-13 16:18:32 Date2 = 2019-12-13 16:18:32 Date3 = 1576253912
Found it. Did not read the manual.
You have to create a 2nd class without the auto-set fields to insert.
public class NameAndDescription {
String name;
String description
}
I think, this is not a good idea.
If you have an autoincrement field in the DB it will get an automatically updated value when you pass 0.
Likewise the default value of the timestamp should be used when passing null or "".
I found the best solution was creating an abstract Dao that implemented the insert and update methods. I didn't get the default value to work (perhaps I was doing something wrong). Take a look at my answer here: How to implement created_at and updated_at column using Room Persistence ORM tools in android

Custom get and set on attribute

I am trying to setup a product key system in my application, but I want to ensure the attribute has the right size (16 characters).
I tried the following
public class ProductKey
{
public const int ProductKeyLength = 16;
[StringLength(ProductKeyLength, MinimumLength = ProductKeyLength)]
private string _value;
[Required]
[Index(IsUnique = true)]
public string Value {
get
{
var temp = Regex.Replace(this._value, ".{4}", "$0-");
return temp.Trim('-');
}
set { this._value = value.Replace("-", "");}
}
}
I want to enable the user to insert the key with our without hyphen. I get the following error with above code:
Column 'Value' in table 'dbo.ProductKeys' is of a type that is invalid for use as a key column in an index.
As I understood, I need to set a limit to Value so it can be used as a unique key. But, _value has a limit and _value is the actual representation of Value in the database.
Is there a way to set the limit correctly in this case?
Thanks in advance.
You are getting the error because without a StringLength attribute on the Value field, the database column gets created as VARCHAR(MAX) which cannot be used as a key. You need a [StringLength] on the field being used as a key. However, as your getter is returning the key formatted with dashes, you need the key length to be 19:
public class ProductKey
{
public const int ProductKeyLength = 19;
private string _value { get; set; }
[Key]
[Required]
[StringLength(ProductKeyLength, MinimumLength = ProductKeyLength)]
[Index(IsUnique = true)]
public string Value
{
get
{
var temp = Regex.Replace(this._value, ".{4}", "$0-");
return temp.Trim('-');
}
set { this._value = value.Replace("-", ""); }
}
}
You might be better off doing your format conversion in ViewModels and client-side code, as one problem you'll have here is searching - for example...
db.Keys.Add(new ProductKey { Value = "1234-5678-9012-3456" });
db.Keys.Add(new ProductKey { Value = "1234567890123455" });
db.SaveChanges();
Console.WriteLine(db.Keys.Count(k => k.Value.Contains("89"))); // 0
Console.WriteLine(db.Keys.Count(k => k.Value.Contains("8-9"))); // 2

java.time.LocalDateTime conversion issue if seconds are 00

My web application is using Apache CXF and JAVA8, and facing below error in response if user send xs:datetime input(seconds 00) as
<urn1:dateTimeVal>2016-04-29T20:00:00</urn1:dateTimeVal>
ERROR :
org.apache.cxf.interceptor.Fault: Marshalling Error:
cvc-datatype-valid.1.2.1: '2016-04-29T20:00' is not a valid value for
'dateTime'.
I debug and analysed that if user send dateTimeVal as 2016-04-29T20:00:00 then CXF validations for input are passed and XML value is UnMarshaled to java.time.LocalDateTime as 2016-05-05T20:00 , and at the time of returning the response, the Marshaling error occurs due to loss of seconds part(00).
Any help/hint are appreciated.
P.S : You can try with below snippet :
java.time.LocalDateTime dt= java.time.LocalDateTime.of(2016, Month.MAY, 5, 20, 00, 00);
System.out.println(dt);
Note : Above code sample is just for understanding to print datetime value. But actual return type expected in web application is java.time.LocalDateTime
OUTPUT EXPECTED : 2016-05-05T20:00:00
OUTPUT ACTUAL : 2016-05-05T20:00
EDIT : The binding (JAXB) content for the field is :
#XmlElement(required = true, type = String.class)
#XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
#XmlSchemaType(name = "dateTime")
#Generated(value = "com.sun.tools.xjc.Driver", date = "2016-05-03T05:28:57+05:30", comments = "JAXB RI v2.2.11")
#NotNull
protected LocalDateTime dateTimeVal;
AND LocalDateTimeAdapter File is
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class LocalDateTimeAdapter
extends XmlAdapter<String, LocalDateTime>
{
public static LocalDateTime parse(String value)
{
DateTimeFormatter dateTimeAndZoneformatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
DateTimeFormatter dateTimeformatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
TemporalAccessor ta = null;
try
{
ta = dateTimeformatter.parse(value);
}
catch (DateTimeParseException ex)
{
ta = dateTimeAndZoneformatter.parse(value);
}
return LocalDateTime.from(ta);
}
public static String print(LocalDateTime value)
{
return value.toString();
}
public LocalDateTime unmarshal(String value)
{
return parse(value);
}
public String marshal(LocalDateTime value)
{
return print(value);
}
}
The problem appears to be in LocalDateTimeAdapter.print(). LocalDateTime.toString() omits the seconds when the seconds value is 0.
If you change it to
public static String print(LocalDateTime value)
{
return value.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
it will provide the seconds as well when marshaling.
To see a quick example, note the results of the following snippet:
System.out.println(LocalDateTime.of(2016,1,1,0,0,0,0).toString());
System.out.println(LocalDateTime.of(2016,1,1,0,0,0,0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
The output it gives is
2016-01-01T00:00
2016-01-01T00:00:00
In the documentation for LocalDateTime.toString() it explains this behavior:
The output will be one of the following ISO-8601 formats:
- uuuu-MM-dd'T'HH:mm
- uuuu-MM-dd'T'HH:mm:ss
- uuuu-MM-dd'T'HH:mm:ss.SSS
- uuuu-MM-dd'T'HH:mm:ss.SSSSSS
- uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS
The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero.
You may want to use
System.out.println (DateTimeFormatter.ISO_LOCAL_DATE_TIME.format (dt));
It gives:
2016-05-05T20:00:00

AspectJ - Is is possible to extend an enum's value?

Say I have an enum
public enum E {A,B,C}
Is it possible to add another value, say D, by AspectJ?
After googling around, it seems that there used to be a way to hack the private static field $VALUES, then call the constructor(String, int) by reflection, but seems not working with 1.7 anymore.
Here are several links:
http://www.javaspecialists.eu/archive/Issue161.html (provided by #WimDeblauwe )
and this: http://www.jroller.com/VelkaVrana/entry/modify_enum_with_reflection
Actually, I recommend you to refactor the source code, maybe adding a collection of valid region IDs to each enumeration value. This should be straightforward enough for subsequent merging if you use Git and not some old-school SCM tool like SVN.
Maybe it would even make sense to use a dynamic data structure altogether instead of an enum if it is clear that in the future the list of commands is dynamic. But that should go into the upstream code base. I am sure the devs will accept a good patch or pull request if prepared cleanly.
Remember: Trying to avoid refactoring is usually a bad smell, a symptom of an illness, not a solution. I prefer solutions to symptomatic workarounds. Clean code rules and software craftsmanship attitude demand that.
Having said the above, now here is what you can do. It should work under JDK 7/8 and I found it on Jérôme Kehrli's blog (please be sure to add the bugfix mentioned in one of the comments below the article).
Enum extender utility:
package de.scrum_master.util;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;
public class DynamicEnumExtender {
private static ReflectionFactory reflectionFactory =
ReflectionFactory.getReflectionFactory();
private static void setFailsafeFieldValue(Field field, Object target, Object value)
throws NoSuchFieldException, IllegalAccessException
{
// let's make the field accessible
field.setAccessible(true);
// next we change the modifier in the Field instance to
// not be final anymore, thus tricking reflection into
// letting us modify the static final field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
int modifiers = modifiersField.getInt(field);
// blank out the final bit in the modifiers int
modifiers &= ~Modifier.FINAL;
modifiersField.setInt(field, modifiers);
FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
fa.set(target, value);
}
private static void blankField(Class<?> enumClass, String fieldName)
throws NoSuchFieldException, IllegalAccessException
{
for (Field field : Class.class.getDeclaredFields()) {
if (field.getName().contains(fieldName)) {
AccessibleObject.setAccessible(new Field[] { field }, true);
setFailsafeFieldValue(field, enumClass, null);
break;
}
}
}
private static void cleanEnumCache(Class<?> enumClass)
throws NoSuchFieldException, IllegalAccessException
{
blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
blankField(enumClass, "enumConstants"); // IBM JDK
}
private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes)
throws NoSuchMethodException
{
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
parameterTypes[0] = String.class;
parameterTypes[1] = int.class;
System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
return reflectionFactory.newConstructorAccessor(enumClass .getDeclaredConstructor(parameterTypes));
}
private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues)
throws Exception
{
Object[] parms = new Object[additionalValues.length + 2];
parms[0] = value;
parms[1] = Integer.valueOf(ordinal);
System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
}
/**
* Add an enum instance to the enum class given as argument
*
* #param <T> the type of the enum (implicit)
* #param enumType the class of the enum to be modified
* #param enumName the name of the new enum instance to be added to the class
*/
#SuppressWarnings("unchecked")
public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) {
// 0. Sanity checks
if (!Enum.class.isAssignableFrom(enumType))
throw new RuntimeException("class " + enumType + " is not an instance of Enum");
// 1. Lookup "$VALUES" holder in enum class and get previous enum
// instances
Field valuesField = null;
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
if (field.getName().contains("$VALUES")) {
valuesField = field;
break;
}
}
AccessibleObject.setAccessible(new Field[] { valuesField }, true);
try {
// 2. Copy it
T[] previousValues = (T[]) valuesField.get(enumType);
List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
// 3. build new enum
T newValue = (T) makeEnum(
enumType, // The target enum class
enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
values.size(), new Class<?>[] {}, // could be used to pass values to the enum constuctor if needed
new Object[] {} // could be used to pass values to the enum constuctor if needed
);
// 4. add new value
values.add(newValue);
// 5. Set new values field
setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
// 6. Clean enum cache
cleanEnumCache(enumType);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
}
Sample application & enum:
package de.scrum_master.app;
/** In honour of "The Secret of Monkey Island"... ;-) */
public enum Command {
OPEN, CLOSE, PUSH, PULL, WALK_TO, PICK_UP, TALK_TO, GIVE, USE, LOOK_AT, TURN_ON, TURN_OFF
}
package de.scrum_master.app;
public class Server {
public void executeCommand(Command command) {
System.out.println("Executing command " + command);
}
}
package de.scrum_master.app;
public class Client {
private Server server;
public Client(Server server) {
this.server = server;
}
public void issueCommand(String command) {
server.executeCommand(
Command.valueOf(
command.toUpperCase().replace(' ', '_')
)
);
}
public static void main(String[] args) {
Client client = new Client(new Server());
client.issueCommand("use");
client.issueCommand("walk to");
client.issueCommand("undress");
client.issueCommand("sleep");
}
}
Console output with original enum:
Executing command USE
Executing command WALK_TO
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant de.scrum_master.app.Command.UNDRESS
at java.lang.Enum.valueOf(Enum.java:236)
at de.scrum_master.app.Command.valueOf(Command.java:1)
at de.scrum_master.app.Client.issueCommand(Client.java:12)
at de.scrum_master.app.Client.main(Client.java:22)
Now you can either add an aspect with an advice executed after the enum class was loaded or just call this manually in your application before extended enum values are to be used for the first time. Here I am showing how it can be done in an aspect.
Enum extender aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Command;
import de.scrum_master.util.DynamicEnumExtender;
public aspect CommandExtender {
after() : staticinitialization(Command) {
System.out.println(thisJoinPoint);
DynamicEnumExtender.addEnum(Command.class, "UNDRESS");
DynamicEnumExtender.addEnum(Command.class, "SLEEP");
DynamicEnumExtender.addEnum(Command.class, "WAKE_UP");
DynamicEnumExtender.addEnum(Command.class, "DRESS");
}
}
Console output with extended enum:
staticinitialization(de.scrum_master.app.Command.<clinit>)
Executing command USE
Executing command WALK_TO
Executing command UNDRESS
Executing command SLEEP
Et voilà! ;-)

Resources