Store sessionScope java.util.TreeMap variable in a document in xPage - xpages

I am working on an application where I am creating a java.util.TreeMap containing data fetched from various other documents of the application and then assigning that treemap to a sessionsScope variable. This is working fine.
Now I want to provide a functionality wherein I need to store this map inside a NotesDocument.
But when I try doing this, I am getting an error.
var doc:NotesDocument = database.createDocument();
doc.replaceItemValue("Form","testForm");
print("json = "+sessionScope.get("Chart_Map"));
doc.replaceItemValue("Calender_Map",sessionScope.get("Chart_Map"));
doc.save();
Exception:
Error while executing JavaScript action expression
Script interpreter error, line=4, col=13: [TypeError] Exception occurred calling method NotesDocument.replaceItemValue(string, java.util.TreeMap) null**
Is it possible to store a java.util.TreeMap in a notesdocument field?
If yes then how to implement that?
If no then why not? has that something to do with serializability?

You can't store Java objects inside Document fields unless you use the MimeDomino Document data source
http://www.openntf.org/main.nsf/blog.xsp?permaLink=NHEF-8XLA83
Or even better the new openntf Domino API that has this functionallity built in
http://www.openntf.org/main.nsf/project.xsp?r=project/OpenNTF%20Domino%20API
using MimeStorage

Fredrik is right, the MimeDomino makes most sense. If you are not ready and your field isn't too big for a normal Notes item, you could use CustomDataBytes as Sven suggested - or you use JSON by subclassing TreeMap. It could look like this:
import java.util.TreeMap;
import java.util.Vector;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import lotus.domino.Item;
import lotus.domino.NotesException;
public class TreeMapItem extends TreeMap<String, String> {
private static final long serialVersionUID = 1L;
public static TreeMapItem load(Item source) throws JsonSyntaxException, NotesException {
Gson g = new Gson();
TreeMapItem result = g.fromJson(source.getText(), TreeMapItem.class);
return result;
}
public void save(Item target) throws NotesException {
Gson g = new Gson();
target.setValueString(g.toJson(this));
}
}
I used Google's Gson, it is quite easy, but you might need to deploy it as plug-in for the Java security to work. There is build in JSON in XPages too - a little more work. An alternate approach would be to use 2 fields in Domino, one to load the keys from and one for the values - it would be in line with Domino practises from classic.
A third approach would be be to store the values separated using a pipe character:
#SuppressWarnings({ "unchecked", "rawtypes" })
public void saveCompact(Item target) throws NotesException {
Vector v = new Vector();
for (Map.Entry<String, String> me : this.entrySet()) {
v.add(me.getKey()+"|"+me.getValue());
}
target.setValues(v);
}
#SuppressWarnings("rawtypes")
public static TreeMapItem loadCompact(Item source) throws NotesException {
TreeMapItem result = new TreeMapItem();
Vector v = source.getValues();
for (Object o : v) {
String[] candidate = o.toString().split("|");
if (candidate.length > 1) {
result.put(candidate[0], candidate[1]);
}
}
return result;
}
Let us know how it works for you

Related

MapStruct unable to generate mapper for XJC (JAXB) generated classes

I'm struggling since a couple of hours trying to get MapStruct generate a valid mapper for JAXB generated classes. The particularity of these classes is that they don't have neither setters nor adders for collections. For example:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "IndividualType", propOrder = {"addressTypes","pensionTypes"})
public class IndividualType
{
...
#XmlElement(name = "addressType")
protected List<AddressType> addressTypes;
#XmlAttribute(name = "firstName", required = true)
protected String firstName;
...
public List<AddressType> getAddressTypes()
{
if (addressTypes == null) {
addressTypes = new ArrayList<AddressType>();
}
return this.addressTypes;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName(String value)
{
this.firstName = value;
}
...
}
The class avove have a getter and a setter for attributes (firstName in this example) but for collections (List here) it only has a getter. Hence it's the consumer responsibility to access via getAddressTypes(add (new AddressType(...)).
The MapStruct mapper for such a class is as follows:
#Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE, uses = {AddressTypeMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public interface IndividualTypeMapper
{
IndividualType toIndividualType(IndividualEntity individual);
#InheritInverseConfiguration
IndividualEntity fromIndividualType(IndividualType individualType);
}
And the MapStruct generated code is:
#Override
public IndividualEntity fromIndividualType(IndividualType individualType)
{
if ( individualType == null )
return null;
IndividualEntity individualEntity = new IndividualEntity();
individualEntity.setFirstName( individualType.getFirstName() );
...
return individualEntity;
}
In the generated code above, only the properties having a setter get initialized despite the usage of the TARGET_IMMUTABLE strategy.
Any suggestions please ? Of course, a simple constructor would perfectly do but, for some reason, people seems to prefer complicated and nonworking solutions to simple working ones and, consequently, I have to use MapStruct :-(
Many thanks in advance.
Marie-France
The reason why it is not working is due to the fact that you are using CollectionMappingStrategy.TARGET_IMMUTABLE. With that you are basically telling MapStruct my collection targets are immutable and will throw an exception if you try to modify the collection returned by the getter.
I would suggest removing the collectionMappingStrategy and see whether it works without it.

Cucumber V5-V6 - passing complex object in feature file step

So I have recently migrated to v6 and I will try to simplify my question
I have the following class
#AllArgsConstructor
public class Songs {
String title;
List<String> genres;
}
In my scenario I want to have something like:
Then The results are as follows:
|title |genre |
|happy song |romance, happy|
And the implementation should be something like:
#Then("Then The results are as follows:")
public void theResultsAreAsFollows(Songs song) {
//Some code here
}
I have the default transformer
#DefaultParameterTransformer
#DefaultDataTableEntryTransformer(replaceWithEmptyString = "[blank]")
#DefaultDataTableCellTransformer
public Object transformer(Object fromValue, Type toValueType) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}
My current issue is that I get the following error: Cannot construct instance of java.util.ArrayList (although at least one Creator exists)
How can I tell cucumber to interpret specific cells as lists? but keeping all in the same step not splitting apart? Or better how can I send an object in a steps containing different variable types such as List, HashSet, etc.
If I do a change and replace the list with a String everything is working as expected
#M.P.Korstanje thank you for your idea. If anyone is trying to find a solution for this here is the way I did it as per suggestions received. Inspected to see the type fromValue has and and updated the transform method into something like:
if (fromValue instanceof LinkedHashMap) {
Map<String, Object> map = (LinkedHashMap<String, Object>) fromValue;
Set<String> keys = map.keySet();
for (String key : keys) {
if (key.equals("genres")) {
List<String> genres = Arrays.asList(map.get(key).toString().split(",", -1));
map.put("genres", genres);
}
return objectMapper.convertValue(map, objectMapper.constructType(toValueType));
}
}
It is somehow quite specific but could not find a better solution :)

Javafx PropertyValueFactory not populating Tableview

This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.

xe:objectData - Object has been removed or recycled

I use an xe:objectData as a datasource for a xp:dataTable. objectData1 uses some Java code to retrieve all documents from a view that match a key ( username ). The Java code looks like this:
package com.isatweb.cois;
import static com.ibm.xsp.extlib.util.ExtLibUtil.getCurrentDatabase;
import static com.ibm.xsp.extlib.util.ExtLibUtil.getCurrentSession;
import java.io.Serializable;
import lotus.domino.Database;
import lotus.domino.Name;
import lotus.domino.Session;
import lotus.domino.View;
import lotus.domino.ViewEntryCollection;
public class ObjectDataVisits implements Serializable {
private static final long serialVersionUID = 1L;
ViewEntryCollection vec = null;
public ObjectDataVisits(){
try {
this.update();
} catch (Exception e) {
System.out.print(e);
}
}
public void update() {
try {
Database _db = getCurrentDatabase();
Session _session = getCurrentSession();
Name nam = _session.createName(_session.getEffectiveUserName());
String username = nam.getAbbreviated().replace(" ", "#").replace("/", "#").toUpperCase();
View view = _db.getView("vw_visit_open");
this.vec = view.getAllEntriesByKey(username);
} catch (Exception e) {
System.out.print(e);
}
}
public ViewEntryCollection getVisits(){
return this.vec;
}
}
The XPage has the following code
When I first load the page, the data is read from the wiew and the dataTable displays the NoteIDs of all matching documents.
When I refresh the page using the button, I get an "Object has been removed or recycled" error.
Can anyone pls. show me what I'm doing wrong? ( and perhaps, how to do it right )
The problem is, that Notes objects are not serializable. During the partial refresh the getVisits() method is executed before the update() method. The ViewEntryCollection is a references to a view, and this view is already recycled.
If you just want to store some note id's then you could store them in a Vector instead. Otherwise you have to call your update() method in your getVisits() method everytime.

Spring LDAP Template Usage

Please take a look at the test class below. I am trying to do an LDAP search with Spring LDAP Template. I am able to search and produce a list of entries corresponding to the search criteria without the Spring LDAP template by using the DirContext as shown in the method searchWithoutTemplate(). But when I use a LdapTemplate, I end up with a NPE as shown further below. I am sure I must be missing something. Can someone help please?
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.core.support.LdapContextSource;
public class LDAPSearchTest {
//bind params
static String url="ldap://<IP>:<PORT>";
static String userName="cn=Directory Manager";
static String password="password123";
static String bindDN="dc=XXX,dc=com";
//search params
static String base = "ou=StandardUser,ou=XXXCustomers,ou=People,dc=XXX,dc=com";
static String filter = "(objectClass=*)";
static String[] attributeFilter = { "cn", "uid" };
static SearchControls sc = new SearchControls();
public static void main(String[] args) throws Exception {
// sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
sc.setReturningAttributes(attributeFilter);
searchWithTemplate(); //NPE
//searchWithoutTemplate(); //works fine
}
public static void searchWithTemplate() throws Exception {
DefaultDirObjectFactory factory = new DefaultDirObjectFactory();
LdapContextSource cs = new LdapContextSource();
cs.setUrl(url);
cs.setUserDn(userName);
cs.setPassword(password);
cs.setBase(bindDN);
cs.setDirObjectFactory(factory.getClass ());
LdapTemplate template = new LdapTemplate(cs);
template.afterPropertiesSet();
System.out.println((template.search(new LdapName(base), filter, sc,
new AttributesMapper() {
public Object mapFromAttributes(Attributes attrs)
throws NamingException {
System.out.println(attrs);
return attrs.get("uid").get();
}
})));
}
public static void searchWithoutTemplate() throws NamingException{
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
//env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, userName);
env.put(Context.SECURITY_CREDENTIALS, password);
DirContext dctx = new InitialDirContext(env);
NamingEnumeration results = dctx.search(base, filter, sc);
while (results.hasMore()) {
SearchResult sr = (SearchResult) results.next();
Attributes attrs = sr.getAttributes();
System.out.println(attrs);
Attribute attr = attrs.get("uid");
}
dctx.close();
}
}
Exception is:
Exception in thread "main" java.lang.NullPointerException
at org.springframework.ldap.core.support.AbstractContextSource.getReadOnlyContext(AbstractContextSource.java:125)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:287)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:237)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:588)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:546)
at LDAPSearchTest.searchWithTemplate(LDAPSearchTest.java:47)
at LDAPSearchTest.main(LDAPSearchTest.java:33)
I am using Spring 2.5.6 and Spring LDAP 1.3.0
A quick scan showed that it's the authenticationSource field of AbstractContextSource that is the culprit. That file includes the following comment on the afterPropertiesSet() method:
/**
* Checks that all necessary data is set and that there is no compatibility
* issues, after which the instance is initialized. Note that you need to
* call this method explicitly after setting all desired properties if using
* the class outside of a Spring Context.
*/
public void afterPropertiesSet() throws Exception {
...
}
That method then goes on to create an appropriate authenticationSource if you haven't provided one.
As your test code above is most definitely not running within a Spring context, and you haven't explicitly set an authenticationSource, I think you need to edit your code as follows:
...
cs.setDirObjectFactory(factory.getClass ());
// Allow Spring to configure the Context Source:
cs.afterPropertiesSet();
LdapTemplate template = new LdapTemplate(cs);

Resources