Grid in Struts2 using struts2-jquery-grid plugin - json

I'm trying with a Struts jQuery grid using the struts2-jquery-grid-3.7.0 plugin as demonstrated on the showcase, Grid (Editable/Multiselect).
The Struts form:
<s:form namespace="/admin_side" action="Test" validate="true" id="dataForm" name="dataForm">
<s:url id="remoteurl" action="TestGrid" namespace="/admin_side"/>
<s:url id="editurl" action="edit-grid-entry"/>
<sjg:grid
id="gridmultitable"
caption="Example (Editable/Multiselect)"
dataType="json"
href="%{remoteurl}"
pager="true"
navigator="true"
navigatorSearchOptions="{sopt:['eq','ne','lt','gt']}"
navigatorAddOptions="{height:280, width:500, reloadAfterSubmit:true}"
navigatorEditOptions="{height:280, width:500, reloadAfterSubmit:false}"
navigatorEdit="true"
navigatorView="true"
navigatorViewOptions="{height:280, width:500}"
navigatorDelete="true"
navigatorDeleteOptions="{height:280, width:500,reloadAfterSubmit:true}"
gridModel="gridModel"
rowList="5,10,15"
rowNum="5"
rownumbers="true"
editurl="%{editurl}"
editinline="true"
multiselect="true"
onSelectRowTopics="rowselect"
>
<sjg:gridColumn name="countryId" index="countryId" title="Id" formatter="integer" editable="false" sortable="true" search="true" sorttype="integer" searchoptions="{sopt:['eq','ne','lt','gt']}"/>
<sjg:gridColumn name="countryName" index="countryName" title="Country Name" editable="true" edittype="text" sortable="true" search="true" sorttype="text"/>
<sjg:gridColumn name="countryCode" index="countryCode" title="Country Code" sortable="true" search="true" editable="true" sorttype="text"/>
</sjg:grid>
</s:form>
The action class:
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value = "json-default")
#InterceptorRefs(#InterceptorRef(value = "store", params = {"operationMode", "AUTOMATIC"}))
public final class TestAction extends ActionSupport implements Serializable
{
#Autowired
private final transient CountryService countryService=null;
private static final long serialVersionUID = 1L;
// Result List
private List<Country> gridModel;
// Get how many rows we want to have into the grid - rowNum attribute in the grid
private Integer rows=5;
// Get the requested page. By default grid sets this to 1.
private Integer page=1;
// sorting order - asc or desc
private String sord;
// get index row - i.e. user click to sort.
private String sidx;
// Search Field
private String searchField;
// The Search String
private String searchString;
// The Search Operation ['eq','ne','lt','le','gt','ge','bw','bn','in','ni','ew','en','cn','nc']
private String searchOper;
// Your Total Pages
private Integer total;
// All Records
private Integer records;
#Action(value = "TestGrid",
results = {
#Result(name = ActionSupport.SUCCESS, type = "json", params = {"includeProperties", "gridModel\\[\\d+\\]\\.countryId, gridModel\\[\\d+\\]\\.countryName, gridModel\\[\\d+\\]\\.countryCode", "excludeNullProperties", "true"})},
interceptorRefs = {
#InterceptorRef(value = "json")})
public String execute() {
records=countryService.rowCount().intValue();
total=new BigDecimal(records).divide(new BigDecimal(rows), 0, BigDecimal.ROUND_CEILING).intValue();
gridModel=countryService.getList(page, rows, null, null);
System.out.println("records "+records+" total "+total+" Page " + getPage() + " Rows " + getRows() + " Sort Order " + getSord() + " Index Row :" + getSidx()+"Search :" + searchField + " " + searchOper + " " + searchString);
return SUCCESS;
}
public String getJSON() {
return execute();
}
public Integer getRows() {
return rows;
}
public void setRows(Integer rows) {
this.rows = rows;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getRecords() {
return records;
}
public void setRecords(Integer records) {
this.records = records;
}
public List<Country> getGridModel() {
return gridModel;
}
public void setGridModel(List<Country> gridModel) {
this.gridModel = gridModel;
}
public String getSord() {
return sord;
}
public void setSord(String sord) {
this.sord = sord;
}
public String getSidx() {
return sidx;
}
public void setSidx(String sidx) {
this.sidx = sidx;
}
public void setSearchField(String searchField) {
this.searchField = searchField;
}
public void setSearchString(String searchString) {
this.searchString = searchString;
}
public void setSearchOper(String searchOper) {
this.searchOper = searchOper;
}
#Action(value = "Test",
results = {
#Result(name = ActionSupport.SUCCESS, location = "Test.jsp"),
#Result(name = ActionSupport.INPUT, location = "Test.jsp")},
interceptorRefs = {
#InterceptorRef(value = "defaultStack", params = {"validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})})
public String load() throws Exception {
return ActionSupport.SUCCESS;
}
}
The next and previous pagination links are always disabled as shown in the following snap shot.
It indicates, page 1 of 1. There are no more pages. Hence, the links are disabled.
In the execute() method, however, records is initialized to 31, total to 7 Page to 1 Rows to 5. All other properties are always null whether or not links/buttons for sorting, searching, editing, adding a row are clicked.
What am I overlooking here?

You forgot to include fields to json result that are necessary for grid to function properly. Change to
#Result(type = "json", params = {"includeProperties", "gridModel\\[\\d+\\]\\.countryId, gridModel\\[\\d+\\]\\.countryName, gridModel\\[\\d+\\]\\.countryCode, total, records, rows, page, sord, sidx, searchField, searchString, searchOper", "excludeNullProperties", "true"})
Also, json interceptor is not necessary, but params is required.
#InterceptorRef("params")

Related

How to sum json object in Java 8

JSON: {productDetails=[{amount=20000.0, rValue=10000.00}, {amount=80000.0, rValue=6000.00} {amount=70000.0, rValue=0}]
I have to get the totalValue based on rValue and amount.
if rValue != 0 then sum only rValues thats is 10000+6000 = 16000 Else, get the amount = 70000
and then totalValue = rvalues + amount (16000+70000)
Please, can anyone suggest how can I in Java8.
List<Map> products= (List)products.get("productDetails");
for (Map<String, Object> scheme : products) {
log.info("scheme " + scheme.toString());
BigDecimal rValue = Optional.ofNullable(scheme.get("rValue "))
.map(Object::toString)
.map(BigDecimal::new)
.orElse(BigDecimal.ZERO);
BigDecimal amount = Optional.ofNullable(scheme.get("amount"))
.map(Object::toString)
.map(BigDecimal::new)
.orElse(BigDecimal.ZERO);
log.debug("rValue : " + rValue );
log.debug("amount: " + amount);
What you need is:
BigDecimal reduced = products.stream().map(x -> {
if (x.getrValue() != BigDecimal.ZERO)
return x.getrValue();
else return x.getAmount();
}).reduce(BigDecimal.ZERO, BigDecimal::add);
Complete example:
public class Ex1 {
public static void main(String[] args) {
List<Product> products = new ArrayList<>(Arrays.asList(
new Product(new BigDecimal(20000), new BigDecimal(10000)),
new Product(new BigDecimal(80000), new BigDecimal(6000)),
new Product(new BigDecimal(70000), BigDecimal.ZERO)
));
BigDecimal reduced = products.stream().map(x -> {
if (x.getrValue() != BigDecimal.ZERO)
return x.getrValue();
else return x.getAmount();
}).reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(reduced);
}
}
class Product {
BigDecimal amount;
BigDecimal rValue;
public Product(BigDecimal amount, BigDecimal rValue) {
this.amount = amount;
this.rValue = rValue;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public BigDecimal getrValue() {
return rValue;
}
public void setrValue(BigDecimal rValue) {
this.rValue = rValue;
}
}
and the result is: 86000
Create POJO object of
long amount, rValue
Turn your JSON to a list of these objects (using Gson / Jackson).
Stream the list and apply your business logic.

ConfirmBehavior dosen't support Ajax rendreing

After an Ajax update of a button with a ConfirmBehavior, all Confirm dialog attributes (Header, Message, Icon) becomes Null.
Its look like thoses values are evaluated during the buildView phase only (applyMetadata function)
In the getHeader()/getMessage()/getIcon() methods of the ConfirmBehavior there is no evaluation of expression.
How to get the real expression at this point ? (to evaluate it during the render phase)
Not a perfect solution
public class ConfirmBehavior extends ClientBehaviorBase {
private String header;
private String message;
private String icon;
#Override
public String getScript(ClientBehaviorContext behaviorContext) {
FacesContext context = behaviorContext.getFacesContext();
UIComponent component = behaviorContext.getComponent();
String source = component.getClientId(context);
if(component instanceof Confirmable) {
String headerExpr = (String) component.getAttributes().get("confirm_header");
if (headerExpr!=null)
this.header = (String) ContextUtil.eval(context, headerExpr);
String messageExpr = (String) component.getAttributes().get("confirm_message");
if (messageExpr!=null)
this.message = (String) ContextUtil.eval(context, messageExpr);
String iconExpr = (String) component.getAttributes().get("confirm_icon");
if (iconExpr!=null)
this.icon = (String) ContextUtil.eval(context, iconExpr);
String script = "PrimeFaces.confirm({source:'" + source + "',header:'" + getHeader() + "',message:'" + getMessage() + "',icon:'" + getIcon() + "'});return false;";
((Confirmable) component).setConfirmationScript(script);
return null;
}
else {
throw new FacesException("Component " + source + " is not a Confirmable. ConfirmBehavior can only be attached to components that implement org.primefaces.component.api.Confirmable interface");
}
}
...
}

gson flat json to nested objects needs serializer/deserializer?

I have some JSON coming in (I don't have any control or ability to change the structure and/or naming within the JSON...important to keep in mind in this question) that has a "flat" structure similar to this:
{
"name": "...",
"email": "...",
"box_background_color": "...",
"box_border_color": "...",
"box_text_color": "...",
...
}
Now, I can just create a simple object that keeps everything flat, like so:
public class Settings {
#SerializedName("name")
private String _name;
#SerializedName("email")
private String _emailAddress;
#SerializedName("box_background_color")
private String _boxBackgroundColor;
#SerializedName("box_border_color")
private String _boxBorderColor;
#SerializedName("box_text_color")
private String _boxTextColor;
...
}
However, I want everything associated with box settings to be in it's own class (BoxSettings). This is more like what I want:
public class Settings {
#SerializedName("name")
private String _name;
#SerializedName("email")
private String _emailAddress;
private BoxSettings _boxSettings
...
}
public class BoxSettings {
#SerializedName("box_background_color")
private String _boxBackgroundColor;
#SerializedName("box_border_color")
private String _boxBorderColor;
#SerializedName("box_text_color")
private String _boxTextColor;
...
}
I know that if the JSON was structured such that the box settings were nested then it would be easy to accomplish what I want, however, I don't have the ability to change the structure of the JSON, so please don't suggest that (I would do it if I could).
My question is this: Is creating an entire TypeAdapter the only way to accomplish what I want or can I still accomplish most of this with annotations? If it is not the only way, how else can I accomplish this without changing the JSON at all?
The following is an example of what I mean by "creating an entire TypeAdapter":
public class SettingsTypeAdapter implements JsonDeserializer<Settings>, JsonSerializer<Settings> {
#Override
public JsonElement serialize(Settings src, Type typeOfSrc, JsonSerializationContext context) {
// Add _name
// Add _emailAddress
// Add BoxSettings._boxBackgroundColor
// Add BoxSettings._boxBorderColor
// Add BoxSettings._boxTextColor
return jsonElement;
}
#Override
public Settings deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// Read _name
// Read _emailAddress
// Read BoxSettings._boxBackgroundColor
// Read BoxSettings._boxBorderColor
// Read BoxSettings._boxTextColor
return settings;
}
}
The TypeAdapter is not the only way, but in this case would be the best way since you can associate the adapter with a Gson instance (or whatever library you are using) and have all your mapping code there.
Another way is to use JAVA reflection. I've used a version of the below code in my projects before but never with JSON and never with nested objects (mostly when there was no other choice or if i wanted to map a SQL result set to a Java object without calling resultSet.get... a lot of times).
This will work in this case.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.json.JSONObject;
public class Main {
public static void main(String[] args) {
try {
String json = "{\"name\": \"test name\", \"email\": \"email#email.com\", \"box_background_color\": \"red\", \"box_border_color\": \"orange\", \"box_text_color\": \"white\", \"test3_var2\":3}";
JSONObject jsonObject = new JSONObject(json);
System.out.println(jsonObject);
System.out.println();
/*
* need to parse JSON into a map of String, Object
*/
Map<String, Object> mapAll = new HashMap<String, Object>();
Iterator<String> iter = jsonObject.keys();
while (iter.hasNext()) {
String key = (String) iter.next();
Object value = jsonObject.get(key);
mapAll.put(key, value);
System.out.println(key + "::::" + value);
}
System.out.println();
/*
* use the mapper to generate the objects
*/
MyMapper<TestClass1> myMapper = new MyMapper<TestClass1>();
TestClass1 result = myMapper.mapToObject(mapAll, TestClass1.class);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyMapper<T> {
#SuppressWarnings("unchecked")
public T mapToObject(Map<String, Object> flatStructure, Class<T> objectClass) {
T result = null;
Field[] fields = null;
try {
// new base object
result = objectClass.newInstance();
// get all of its fields
fields = objectClass.getDeclaredFields();
for (Field field : fields) {
// normal variable
if (field.isAnnotationPresent(MyColumn.class)) {
String variableKey = field.getAnnotation(MyColumn.class).variableKey();
setJavaFieldValue(result, field.getName(), flatStructure.get(variableKey));
}
// variable that is an object and itself has to be mapped
else if (field.isAnnotationPresent(MyInnerColumn.class)) {
String startsWith = field.getAnnotation(MyInnerColumn.class).startsWith();
// reduce the map to only have attributes that are related to this field
Map<String, Object> reducedMap = reduceMap(startsWith, flatStructure);
// make sure that there are attributes for the inner object
if (reducedMap != null) {
// map the inner object
MyMapper<T> myMapper = new MyMapper<T>();
T t2 = myMapper.mapToObject(reducedMap, (Class<T>) field.getType());
// set the mapped object to the base objecct
setJavaFieldValue(result, field.getName(), t2);
}
} else {
// no annotation on the field so ignored
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private Map<String, Object> reduceMap(String startsWith, Map<String, Object> mapToReduce) {
Map<String, Object> result = new HashMap<String, Object>();
for (Map.Entry<String, Object> entry : mapToReduce.entrySet()) {
if (entry.getKey().toLowerCase().startsWith(startsWith.toLowerCase())) {
result.put(entry.getKey(), entry.getValue());
}
}
return result.size() == 0 ? null : result;
}
private void setJavaFieldValue(Object object, String fieldName, Object fieldValue) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
boolean fieldAccess = field.isAccessible();
// make the field accessible
field.setAccessible(true);
field.set(object, fieldValue);
// put it back to the way it was
field.setAccessible(fieldAccess);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
* Annotation for a regular variable / field
*/
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#interface MyColumn {
// the variable's JSON key
String variableKey() default "";
}
/*
* Annotation for an inner / nested variable / field
*/
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#interface MyInnerColumn {
/*
* JSON keys that start with this string will be
* associated with this nested field
*/
String startsWith() default "";
}
class TestClass1 {
#MyColumn(variableKey = "name")
private String _name;
#MyColumn(variableKey = "email")
private String _emailAddress;
#MyInnerColumn(startsWith = "box_")
private TestClass2 innerClass;
#MyInnerColumn(startsWith = "test3_")
private TestClass3 innerClass2;
#Override
public String toString() {
return "TestClass1 [_name=" + _name + ", _emailAddress=" + _emailAddress + ", innerClass=" + innerClass + ", innerClass2=" + innerClass2 + "]";
}
}
class TestClass2 {
#MyColumn(variableKey = "box_background_color")
private String _boxBackgroundColor;
#MyColumn(variableKey = "box_border_color")
private String _boxBorderColor;
#MyColumn(variableKey = "box_text_color")
private String _boxTextColor;
#Override
public String toString() {
return "TestClass2 [_boxBackgroundColor=" + _boxBackgroundColor + ", _boxBorderColor=" + _boxBorderColor
+ ", _boxTextColor=" + _boxTextColor + "]";
}
}
class TestClass3 {
#MyColumn(variableKey = "test3_var1")
private String _test3Var1;
#MyColumn(variableKey = "test3_var2")
private int _test3Var2;
#Override
public String toString() {
return "TestClass3 [_test3Var1=" + _test3Var1 + ", _test3Var2=" + _test3Var2 + "]";
}
}
Output
{"box_background_color":"red","box_text_color":"white","test3_var2":3,"name":"test name","email":"email#email.com","box_border_color":"orange"}
box_background_color::::red
box_text_color::::white
test3_var2::::3
name::::test name
email::::email#email.com
box_border_color::::orange
TestClass1 [_name=test name, _emailAddress=email#email.com, innerClass=TestClass2 [_boxBackgroundColor=red, _boxBorderColor=orange, _boxTextColor=white], innerClass2=TestClass3 [_test3Var1=null, _test3Var2=3]]

How to Seed DB after DontDropDbJustCreateTablesIfModelChanged

Recently I've had my DB rights reduced so that I can't drop and recreate databases. This has led to me using the DontDropDbJustCreateTablesIfModelChanged Database initialisation from nuget.
However I'm now stuck as to how I should seed data as the Seed function is not in the initialisation so I can't override it. This is what I'd like to be able to do.
public class MyDBInitialiser : DontDropDbJustCreateTablesIfModelChanged<MyContext>
{
protected override void Seed(MyContext context)
{
base.Seed(context);
context.Item.Add(new Item() { ItemId = 1, Name = "Item 1"});
context.Item.Add(new Item() { ItemId = 2, Name = "Item 2"});
context.Item.Add(new Item() { ItemId = 3, Name = "Item 3"});
}
}
Is there another way of seeding data in this situation.
Simply,
public class DontDropDbJustCreateTablesIfModelChanged<T>
: IDatabaseInitializer<T> where T : DbContext
{
private EdmMetadata _edmMetaData;
public void InitializeDatabase(T context)
{
ObjectContext objectContext =
((IObjectContextAdapter)context).ObjectContext;
string modelHash = GetModelHash(objectContext);
if (CompatibleWithModel(modelHash, context, objectContext))
return;
DeleteExistingTables(objectContext);
CreateTables(objectContext);
SaveModelHashToDatabase(context, modelHash, objectContext);
Seed(context);
}
protected virtual void Seed(T context) { }
private void SaveModelHashToDatabase(T context, string modelHash,
ObjectContext objectContext)
{
if (_edmMetaData != null) objectContext.Detach(_edmMetaData);
_edmMetaData = new EdmMetadata();
context.Set<EdmMetadata>().Add(_edmMetaData);
_edmMetaData.ModelHash = modelHash;
context.SaveChanges();
}
private void CreateTables(ObjectContext objectContext)
{
string dataBaseCreateScript =
objectContext.CreateDatabaseScript();
objectContext.ExecuteStoreCommand(dataBaseCreateScript);
}
private void DeleteExistingTables(ObjectContext objectContext)
{
objectContext.ExecuteStoreCommand(Dropallconstraintsscript);
objectContext.ExecuteStoreCommand(Deletealltablesscript);
}
private string GetModelHash(ObjectContext context)
{
var csdlXmlString = GetCsdlXmlString(context).ToString();
return ComputeSha256Hash(csdlXmlString);
}
private bool CompatibleWithModel(string modelHash, DbContext context,
ObjectContext objectContext)
{
var isEdmMetaDataInStore =
objectContext.ExecuteStoreQuery<int>(LookupEdmMetaDataTable)
.FirstOrDefault();
if (isEdmMetaDataInStore == 1)
{
_edmMetaData = context.Set<EdmMetadata>().FirstOrDefault();
if (_edmMetaData != null)
{
return modelHash == _edmMetaData.ModelHash;
}
}
return false;
}
private string GetCsdlXmlString(ObjectContext context)
{
if (context != null)
{
var entityContainerList = context.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace);
if (entityContainerList != null)
{
var entityContainer = entityContainerList.FirstOrDefault();
var generator =
new EntityModelSchemaGenerator(entityContainer);
var stringBuilder = new StringBuilder();
var xmlWRiter = XmlWriter.Create(stringBuilder);
generator.GenerateMetadata();
generator.WriteModelSchema(xmlWRiter);
xmlWRiter.Flush();
return stringBuilder.ToString();
}
}
return string.Empty;
}
private static string ComputeSha256Hash(string input)
{
byte[] buffer = new SHA256Managed()
.ComputeHash(Encoding.ASCII.GetBytes(input));
var builder = new StringBuilder(buffer.Length * 2);
foreach (byte num in buffer)
{
builder.Append(num.ToString("X2",
CultureInfo.InvariantCulture));
}
return builder.ToString();
}
private const string Dropallconstraintsscript =
#"select
'ALTER TABLE ' + so.table_name + ' DROP CONSTRAINT '
+ so.constraint_name
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS so";
private const string Deletealltablesscript =
#"declare #cmd varchar(4000)
declare cmds cursor for
Select
'drop table [' + Table_Name + ']'
From
INFORMATION_SCHEMA.TABLES
open cmds
while 1=1
begin
fetch cmds into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds
deallocate cmds";
private const string LookupEdmMetaDataTable =
#"Select COUNT(*)
FROM INFORMATION_SCHEMA.TABLES T
Where T.TABLE_NAME = 'EdmMetaData'";
}
&
public class Population : DontDropDbJustCreateTablesIfModelChanged</* DbContext */>
{
protected override void Seed(Syndication Context)
{
/* Seeding :) */
}
}
&
Database.SetInitializer</* DbContext */>(new Population());
I my projects I split the db initialization from the db seeding. If you use inversion of control, you should be able to do something like this in your composition root (Application_Start if you are consuming the DbContext from a web app):
var seeder = ServiceLocatorPattern
.ServiceProviderLocator.Current.GetService<ISeedDb>();
if (seeder != null) seeder.Seed();
The interface:
public interface ISeedDb, IDisposable
{
void Seed();
}
A possible implementation:
public class MyDbSeeder : ISeedDb
{
private readonly MyContext _context;
public MyDbSeeder(MyContext context)
{
_context = context;
}
public void Seed()
{
_context.Item.Add(new Item { ItemId = 1, Name = "Item 1" });
// ... etc
}
public void Dispose()
{
_context.Dispose();
}
}

How to handle unidirectional many-to-many relations with Ebean

I have a problem with Ebean. I have the usual Objects PsecUser, PsecRoles and PsecPermission.
A user can have many Permissions or Roles and a Role can have many Permission.
Here the code (extract):
#Entity
public class PsecPermission {
#Id
#GeneratedValue
private Long id;
#Column(unique=true, nullable=false)
private String name;
#Column(nullable=false)
private String type = PsecBasicPermission.class.getName();
#Column(nullable=false)
private String target;
#Column(nullable=false)
private String actions;
}
#Entity
public class PsecRole {
#Id
#GeneratedValue
private Long id;
#Column(unique=true, nullable=false)
private String name;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdate;
#ManyToMany(fetch=FetchType.EAGER)
private List<PsecPermission> psecPermissions;
private boolean defaultRole = false;
}
I wrote the following helper-method:
public PsecRole createOrUpdateRole(String name, boolean defaultRole, String... permissions) {
PsecRole result = server.find(PsecRole.class).
where().eq("name", name).findUnique();
if (result == null) {
result = new PsecRole();
result.setName(name);
}
final List<PsecPermission> permissionObjects = server.find(PsecPermission.class).
where().in("name", (Object[])permissions).findList();
result.setPsecPermissions(permissionObjects);
result.setDefaultRole(defaultRole);
final Set <ConstraintViolation <PsecRole>> errors =
Validation.getValidator().validate(result);
if (errors.isEmpty()) {
server.save(result);
server.saveManyToManyAssociations(result, "psecPermissions");
} else {
log.error("Can't save role: " + name +"!");
for (ConstraintViolation <PsecRole> constraintViolation : errors) {
log.error(" " + constraintViolation);
}
}
return result;
}
and try the following test:
#Test
public void testCreateOrUpdateRole() {
String[] permNames = {"Test1", "Test2", "Test3"};
List <PsecPermission> permissions = new ArrayList <PsecPermission>();
for (int i = 0; i < permNames.length; i++) {
helper.createOrUpdatePermission(permNames[i], "target"+ i, "actions" +i);
PsecPermission perm = server.find(PsecPermission.class).where().eq("name", permNames[i]).findUnique();
assertThat(perm.getTarget()).isEqualTo("target" + i);
assertThat(perm.getActions()).isEqualTo("actions" + i);
permissions.add(perm);
}
PsecRole orgRole = helper.createOrUpdateRole(ROLE, false, permNames);
testRole(permNames, orgRole);
PsecRole role = server.find(PsecRole.class).where().eq("name", ROLE).findUnique();
testRole(permNames, role);
}
private void testRole(String[] permNames, PsecRole role) {
assertThat(role).isNotNull();
assertThat(role.getName()).isEqualTo(ROLE);
assertThat(role.isDefaultRole()).isEqualTo(false);
assertThat(role.getPermissions()).hasSize(permNames.length);
}
Which fails if it checks the number of permissions at the readed role. It's always 0.
I looked into the database and found that psec_role_psec_permission is alway empty.
Any idea what's wrong with the code?
You can get a pure Ebean-example from https://github.com/opensource21/ebean-samples/downloads it uses the eclipse-plugin from ebean.
There are two solutions for this problem:
Simply add cascade option at PsceRole
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
private List<PsecPermission> psecPermissions;
and remove server.saveManyToManyAssociations(result, "psecPermissions"); you find it in the cascade-solution-branch.
The cleaner solution, because you don't need to define cascase- perhaps you don't want it:
Just don't replace the list, just add your entries to the list. Better is to add new and remove old one. This mean in createOrUpdateRole:
result.getPsecPermissions().addAll(permissionObjects);
instead of
result.setPsecPermissions(permissionObjects);