This question already has answers here:
Android Room - simple select query - Cannot access database on the main thread
(23 answers)
Closed 8 months ago.
I´m new to Kotlin and trying to learn it by programming an app for work time recording.
I´ve created a room database which works fine when inserting data. In the next step I would like to retrieve the sum of a column and store this value in a variable. And this is the point where I get stuck. Here are the affected snippets of my code.
data class sumPojo(var sumOvertime: Double)
#Dao
interface OvertimeDao {
#Query(value = "SELECT SUM(overtime) as sumOvertime FROM TableOvertime")
fun getSumOvertime(): sumPojo
}
class OvertimeRepository(private val overtimeDao: OvertimeDao) {
val getSumOvertime: sumPojo = overtimeDao.getSumOvertime()
}
class OvertimeViewModel(application: Application): AndroidViewModel(application) {
private val repository : OvertimeRepository
val getSumOvertime : sumPojo
init {
val overtimeDao = OvertimeDatabase.getDatabase(application).overtimeDao()
repository = OvertimeRepository(overtimeDao)
getSumOvertime = repository.getSumOvertime
}
}
class inputWorktimeFragment : Fragment() {
private lateinit var mOvertimeViewModel : OvertimeViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_input_worktime, container, false)
mOvertimeViewModel = ViewModelProvider(this)[OvertimeViewModel::class.java]
val sumOvertime: sumPojo = mOvertimeViewModel.getSumOvertime
return view
}
}
Following the error message which I receive.
2022-07-06 16:24:03.067 7338-7338/com.example.workingtimerecorder E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.workingtimerecorder, PID: 7338
java.lang.RuntimeException: Cannot create an instance of class com.example.workingtimerecorder.data.OvertimeViewModel
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:320)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:278)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:128)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at com.example.workingtimerecorder.fragments.inputWorktimeFragment.onCreateView(InputWorktimeFragment.kt:55)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3104)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:524)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2879)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2886)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1455)
at android.app.Activity.performStart(Activity.java:8076)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3660)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:312)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:278)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:128)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at com.example.workingtimerecorder.fragments.inputWorktimeFragment.onCreateView(InputWorktimeFragment.kt:55)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3104)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:524)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2879)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2886)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1455)
at android.app.Activity.performStart(Activity.java:8076)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3660)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:469)
at androidx.room.RoomDatabase.query(RoomDatabase.java:525)
at androidx.room.util.DBUtil.query(DBUtil.java:86)
2022-07-06 16:24:03.068 7338-7338/com.example.workingtimerecorder E/AndroidRuntime: at com.example.workingtimerecorder.data.OvertimeDao_Impl.getSumOvertime(OvertimeDao_Impl.java:124)
at com.example.workingtimerecorder.data.OvertimeRepository.<init>(OvertimeRepository.kt:16)
at com.example.workingtimerecorder.data.OvertimeViewModel.<init>(OvertimeViewModel.kt:24)
... 40 more
I would appreciate your help or any hints which could lead me into the rigth direction. I`ve read about several similar issues here on Stackoverflow but none of them could help me.
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
I'd try returning either flow or live data and using view model scope to start a co routine.
in the dao
//change
fun getSumOvertime(): sumPojo
to
fun getSumOvertime(): LiveData<sumPojo>
//or
suspend fun getSumOvertime(): Flow<sumPojo>
in the repo
//livedata
fun getSumPojo():LiveData<sumPojo>{return yourdb.yourdao.getSumovertime}
//flow
suspend fun getSumPojo():Flow<sumPojo>{return yourdb.yourdao.getSumovertime}
in the view model id gain access to the values like such
// create private and public accessors
private var _sumPojo = MutableLiveData<SumPojo> //use Int as mutable type if you do not have an object to map to
val sumPojo: LiveData<SumPojo> // again if you do not have an object for sumPojo use Int as live data type
get() = _sumPojo
private fun refreshSumPojo()=viewModelScope.launch{return yourRepo.getSumPojo()}
init {_sumPojo.value = refreshSumPojo}
in the view I would use databinding and bind the text view to the public accessor
in your view.xml file
<TextView
android:text="#{viewModel.sumPojo.toString()}"
" />
I an enum field in my model,when I use find method to query some data with this field as condition, it return as wanted.
but, when I use bulkOps and excute an upsert operation,it tells me:
can't serialize class com.timanetworks.tpc.vehicle.alarm.domain.enums.FaultType
I am try to add name() method of enum to resolve this problem, and it's success! But I still can not understand why? does any one know?
my model is like this:
#Document(collection = "FaultSnapshot")
#TypeAlias("faultSnapshot")
public class FaultSnapshot extends BaseDocument {
private FaultType type;
private String vin;
private Integer faultLevel;
private Boolean isFault;
private Date time;
...setters and getters...
and FaultType is an enum:
public enum FaultType {
FAULT_EMS,
....
}
and this is find code:
Query query = new Query();
if (type != null) {
query.addCriteria(where("type").is(type));
}
Pageable pageable = new PageRequest(pageIndex - 1, pageSize);
Sort sort = new Sort(Sort.Direction.DESC, "time");
List<FaultHistory> histories = template.find(query.with(pageable).with(sort),
FaultHistory.class);
return new Page<>(count, histories);
this is bulkOps code:
public void upsertSnapshot(Collection<FaultSnapshot> snapshots) {
BulkOperations bulk = template.bulkOps(BulkOperations.BulkMode.UNORDERED, FaultSnapshot.class);
for (FaultSnapshot snapshot : snapshots) {
Query query = new Query();
query.addCriteria(where("vin").is(snapshot.getVin()));
query.addCriteria(where("type").is(snapshot.getType().name()));
Update update = new Update()
.set("isFault", snapshot.getFault())
.set("faultLevel", snapshot.getFaultLevel())
.set("time", snapshot.getTime())
.set("vin", snapshot.getVin())
.set("type", snapshot.getType().name());
bulk.upsert(query, update);
}
bulk.execute();
}
finally,this is the error stack:
Exception in thread "Thread-11" java.lang.IllegalArgumentException: can't serialize class com.timanetworks.tpc.vehicle.alarm.domain.enums.FaultType
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:299)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:194)
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:255)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:194)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:136)
at com.mongodb.DefaultDBEncoder.writeObject(DefaultDBEncoder.java:36)
at com.mongodb.OutMessage.putObject(OutMessage.java:289)
at com.mongodb.OutMessage.writeUpdate(OutMessage.java:180)
at com.mongodb.OutMessage.update(OutMessage.java:60)
at com.mongodb.DBCollectionImpl$Run$1.executeWriteProtocol(DBCollectionImpl.java:908)
at com.mongodb.DBCollectionImpl$Run$RunExecutor.executeWriteProtocol(DBCollectionImpl.java:1025)
at com.mongodb.DBCollectionImpl$Run$RunExecutor.execute(DBCollectionImpl.java:1016)
at com.mongodb.DBCollectionImpl$Run.executeUpdates(DBCollectionImpl.java:917)
at com.mongodb.DBCollectionImpl$Run.execute(DBCollectionImpl.java:859)
at com.mongodb.DBCollectionImpl.executeBulkWriteOperation(DBCollectionImpl.java:169)
at com.mongodb.DBCollection.executeBulkWriteOperation(DBCollection.java:1904)
at com.mongodb.DBCollection.executeBulkWriteOperation(DBCollection.java:1899)
at com.mongodb.BulkWriteOperation.execute(BulkWriteOperation.java:116)
at org.springframework.data.mongodb.core.DefaultBulkOperations.execute(DefaultBulkOperations.java:276)
at com.timanetworks.tpc.vehicle.alarm.dao.FaultSnapshotRepositoryImpl.***upsertSnapshot***(FaultSnapshotRepositoryImpl.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
This is actually a bug (DATAMONGO-1678) in Spring Data MongoDB. It has already been fixed for 1.9.12, 1.10.5 and 2.0.0.RC1.
At the time of writing none of the mentioned versions has been released.
Using the example code below as context... When I run this query I get the 'Id' field coming back as default value (which is 0 for an int). I would like to tell dapper to run in a manner where it would throw an exception if there is a column in the result set that does not get mapped to a property on my result object. (I understand that the issue is just that I need to remove the extra 'd' in the SQL query but I'm interested in having this expose itself more explicitly)
I've been unable to find anything on this topic. Please let me know if this is even possible with Dapper.
Thanks in advance (besides this issue, and for anyone who hasn't taken the plunge, Dapper really is the greatest thing since sliced bread!).
class CustomerRecord
{
public int Id { get; set; }
public string Name { get; set; }
}
CustomerRecord[] GetCustomerRecords()
{
CustomerRecord[] ret;
var sql = #"SELECT
CustomerRecordId AS Idd,
CustomerName as Name
FROM CustomerRecord";
using (var connection = new SqlConnection(this.connectionString))
{
ret = connection.Query<CustomerRecord>(sql).ToArray();
}
return ret;
}
You could create your own type map where you use Dapper's DefaultTypeMap and throw an exception when it cannot find the member:
public class ThrowWhenNullTypeMap<T> : SqlMapper.ITypeMap
{
private readonly SqlMapper.ITypeMap _defaultTypeMap = new DefaultTypeMap(typeof(T));
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
return _defaultTypeMap.FindConstructor(names, types);
}
public ConstructorInfo FindExplicitConstructor()
{
return _defaultTypeMap.FindExplicitConstructor();
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return _defaultTypeMap.GetConstructorParameter(constructor, columnName);
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
var member = _defaultTypeMap.GetMember(columnName);
if (member == null)
{
throw new Exception();
}
return member;
}
}
Downside of this, is that you have to configure all the type maps for every entity:
SqlMapper.SetTypeMap(typeof(CustomerRecord), typeof(ThrowWhenNullTypeMap<CustomerRecord>));
This could be configured using reflection, however.
I came here after I solved this same problem for the IEnumerable<dynamic> methods in Dapper. Then I found the proposal to solve the issue for Query<T>; but that doesn't seem to be going anywhere.
My answer builds on the answer proposed by #HenkMollema, and uses his class in the solution, so credit to him for that...
To solve the IEnumerable<dynamic> scenario, I had created a "SafeDynamic" class (follow the link above to see that). I refactored the static "Create" method into an extension method:
public static class EnumerableDynamicExtensions
{
public static IEnumerable<dynamic> Safe(this IEnumerable<dynamic> rows)
{
return rows.Select(x => new SafeDynamic(x));
}
}
and then I created a DapperExtensions class to provide 'Safe' versions of Query and Read (Read is used after QueryMultiple), to give me...
internal static class DapperExtensions
{
public static IEnumerable<dynamic> SafeQuery(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
return cnn.Query(sql, param, transaction, buffered, commandTimeout, commandType).Safe();
}
public static IEnumerable<dynamic> SafeRead(this SqlMapper.GridReader gridReader, bool buffered = true)
{
return gridReader.Read(buffered).Safe();
}
}
So to solve this issue I added a "SafeQuery<T>" method to DapperExtensions, which takes care of setting up that type mapping for you:
private static readonly IDictionary<Type, object> TypesThatHaveMapper = new Dictionary<Type, object>();
public static IEnumerable<T> SafeQuery<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
if (TypesThatHaveMapper.ContainsKey(typeof(T)) == false)
{
SqlMapper.SetTypeMap(typeof(T), new ThrowWhenNullTypeMap<T>());
TypesThatHaveMapper.Add(typeof(T), null);
}
return cnn.Query<T>(sql, param, transaction, buffered, commandTimeout, commandType);
}
So if the original poster changes the call to Query to become SafeQuery, it should do what he requested
Edit 25/1/17
Improvements to avoid threading issues on the static dictionary:
private static readonly ConcurrentDictionary<Type, object> TypesThatHaveMapper = new ConcurrentDictionary<Type, object>();
public static IEnumerable<T> SafeQuery<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = default(int?), CommandType? commandType = default(CommandType?))
{
TypesThatHaveMapper.AddOrUpdate(typeof(T), AddValue, UpdateValue);
return cnn.Query<T>(sql, param, transaction, buffered, commandTimeout, commandType);
}
private static object AddValue(Type type)
{
SqlMapper.SetTypeMap(type, XXX); // Apologies... XXX is left to the reader, as my implementation has moved on significantly.
return null;
}
private static object UpdateValue(Type type, object existingValue)
{
return null;
}
I'd like to expand on #Richardissimo 's answer by providing a visual studio project that includes his "SafeQuery" extention to Dapper, wrapped up nice and neat and tested.
https://github.com/LarrySmith-1437/SafeDapper
I use this in all my projects now to help keep the DAL clean of mismapped data, and felt the need to share. I would have posted up a Nuget, but the dependency on Dapper itself makes it much easier to post the project where consumers can update the reference to the Dapper version they want. Consume in good health, all.
Based on this thread and some other resources on SO, I've created an extension method without any custom mapper. What I needed was to throw when some property of my DTO was not set because for example SQL query has some column missing in SELECT statement.
This way my DTO would be set with default property silently and that's kinda dangerous.
The code can be simplified a little by not checking firstly for all properties being present in result, but throwing exception in the last Select call where we could iterate through properties of our type and check if query result has this property as well.
public static class Extensions
{
public static async Task<IEnumerable<T>> SafeQueryAsync<T>(
this IDbConnection cnn,
string sql,
object param = null,
IDbTransaction transaction = null,
int? commandTimeout = default(int?),
CommandType? commandType = default(CommandType?))
where T : new()
{
Dictionary<string, PropertyInfo> propertySetters = typeof(T)
.GetProperties().Where(p => p.CanRead && p.CanWrite)
.ToDictionary(p => p.Name.ToLowerInvariant(), p => p);
HashSet<string> typeProperties = propertySetters
.Select(p => p.Key)
.ToHashSet();
var rows = (await cnn.QueryAsync(sql, param, transaction, commandTimeout, commandType)).ToArray();
if (!rows.Any())
{
return Enumerable.Empty<T>();
}
var firstRow = rows.First();
HashSet<string> rowColumns = ((IDictionary<string, object>) firstRow)
.Select(kvp=>kvp.Key.ToLowerInvariant()).ToHashSet();
var notMappedColumns = typeProperties.Except(rowColumns).ToArray();
if (notMappedColumns.Any())
{
throw new InvalidOperationException(
$"Not all type properties had corresponding columns in SQL query. Query result lacks [{string.Join(", ", notMappedColumns)}]");
}
return rows.Select(row =>
{
IDictionary<string, object> rowDict = (IDictionary<string, object>) row;
T instance = new T();
rowDict.Where(o => propertySetters.ContainsKey(o.Key.ToLowerInvariant()))
.ToList().ForEach(o => propertySetters[o.Key.ToLowerInvariant()].SetValue(instance, o.Value));
return instance;
}).AsEnumerable();
}
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I read the xml file with linq and create list of objects.
StringReader stream=new StringReader(xml);
XmlTextReader reader=new XmlTextReader(stream);
XElement req = XElement.Load(reader);
var users= (req.Descendants("Report")
.Select(e => new {
Fname= e.Descendants("firstName").FirstOrDefault().Value,
Lname = e.Descendants("lastName").FirstOrDefault().Value,
personalId = e.Descendants("id").FirstOrDefault().Value,
})).ToList();
the users value include 100,000 objects.
I want bulk insert these objects into a database table.
public static void saveData<T>(ref List<T> list, string destinationTableName, int batchSize)
{
using (EntityDataReader<T> reader = new EntityDataReader<T>(list))
using (System.Data.SqlClient.SqlBulkCopy sbc = new System.Data.SqlClient.SqlBulkCopy("your connection string"))
{
for (int i = 0; i < reader.FieldCount; i++)
{
string colName = reader.GetName(i);
sbc.ColumnMappings.Add(colName, colName);
}
sbc.BatchSize = batchSize;
sbc.DestinationTableName = destinationTableName;
sbc.WriteToServer(reader);
}
}
I'm using this code to insert a very large list of items, T should be a known entity object
Does Spring Expression Language support IN operator? Similar to SQL IN clause.
public class Security {
private secTyp1;
public Security (String aSecTyp1) {
secTyp1 = aSecTyp1;
}
}
Security security = new Security("BOND");
StandardEvaluationContext context = new StandardEvaluationContext(security);
ExpressionParser parser = new SpelExpressionParser();
// This should return true
boolean result = parser.parseExpression("secTyp1 **IN** {'BOND','SWPI'}").getValue(context, Boolean.class);
// This should return false
result = parser.parseExpression("secTyp1 **IN** {'FUT','SWPI'}").getValue(context, Boolean.class);
I get the following exception
org.springframework.expression.spel.SpelParseException: EL1041E:(pos 8): After parsing a valid expression, there is still more data in the expression: 'IN'
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:118)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:56)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:1)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:66)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:56)
at com.rules.AssignableSecurityRule.evaluateInCondition(AssignableSecurityRule.java:48)
at com.rules.AssignableSecurityRuleTest.testINCondition(AssignableSecurityRuleTest.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
It does not support the IN operator because you do not need it. You can use the Method invokation feature instead.
So, invoking .contains(..) on a list will do what you want.
You can try those two solutions
#Test
public void solutionOneTest() {
final Security security = new Security("BOND");
final EvaluationContext context = new StandardEvaluationContext(security);
Boolean contains = PARSER.parseExpression("{'BOND','SWPI'}.contains(#root.secTyp1)").getValue(context, Boolean.class);
Assert.assertTrue(contains);
contains = PARSER.parseExpression("{'FUT','SWPI'}.contains(#root.secTyp1)").getValue(context, Boolean.class);
Assert.assertFalse(contains);
}
#Test
public void solutionTwoTest() {
final Security security = new Security("BOND");
final EvaluationContext context = new StandardEvaluationContext();
context.setVariable("sec", security);
Boolean contains = PARSER.parseExpression("{'BOND','SWPI'}.contains(#sec.secTyp1)").getValue(context, Boolean.class);
Assert.assertTrue(contains);
contains = PARSER.parseExpression("{'FUT','SWPI'}.contains(#sec.secTyp1)").getValue(context, Boolean.class);
Assert.assertFalse(contains);
}
In addition to calling methods directly as in micfra's answer, SpEL also supports some pretty powerful collection filtering expressions as well. Collection Selection allows you to filter out a sublist of matching values, when you could then test for size:
!{'BOND','SWPI'}.?[#this == 'BOND'].empty
Where this is really handy is filtering lists of complex objects:
!securities.?[secTyp1 == 'BOND'].empty