I am using PowerMockito to verify the number of calls on a private method: loadProperties().
I also have that method stubbed to return a pre-defined value.
Even though I have the method stubbed, the real implementation is being called, and throwing a NullPointerException, since its dependency (the "loader" variable, see snippet below) is not defined (nor should it be) for this test.
As an experiment, I changed the method to be public, and then it works fine!
I am thinking it is a bug in PowerMockito, but I have been wrong about more certain things than this!
Here is the code
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.times;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;
#RunWith(PowerMockRunner.class)
#PrepareForTest({DArgumentsLoader.class})
public class TestConfig {
#Test
public void testGetPropertyMapCalledOnce() throws Exception {
Config configMock = mock(Config.class);
Map<String, String> testMap = new HashMap<String, String>();
testMap.put("xx", "xx");
when(configMock, "loadProperties").thenReturn(testMap);
when(configMock.getString(anyString(), anyString())).thenCallRealMethod();
// call it twice
configMock.getString("xx", "yy");
configMock.getString("xx", "yy");
// verify loadProperties was only called once
PowerMockito.verifyPrivate(configMock, times(1)).invoke("loadProperties");
}
}
Just to clarify, the Config class looks like this
private Map<String, String> loadProperties() throws IOException, HttpException {
return loader.loadProperties();
}
public String getString(String key, final String defaultValue) {
String value = getPropertyMap().get(key);
if(value != null) {
return value;
} else {
return defaultValue;
}
}
private Map<String, String> getPropertyMap() throws LoadException {
if(propertyMap == null) {
propertyMap = loadProperties();
}
return propertyMap;
}
The loadProperties() method should not be called at all, in view of the fact that the tester says
when(configMock, "loadProperties").thenReturn(testMap);
But it is being called, and it is throwing a NullPointerException. Is that a bug or a feature?
You are not preparing the Config class for mocking, so PowerMock cannot handle it. Just add it to the #PrepareForTest annotation, and you should be fine:
#PrepareForTest({Config.class, DArgumentsLoader.class})
Related
I am using R2dbcMessageSource to query a table for an item and want to use one of the columns to create a message to send to a message handler.
The result of the query is a Message<Mono<Event>>, which makes perfect sense. I want to take event.getDetails and create a Message.
Using a DirectChannel and Transformer, I tried something like this
#Bean
#Transformer(inputChannel = "fromR2dbcChannel", outputChannel = "fromTransformer")
public GenericTransformer<Message<Mono<Event>>, Message<String>> monoToString() {
return message -> {
Mono<Event> payload = message.getPayload();
final String details = payload.block();
if (details == null) {
return null;
}
return MessageBuilder
.withPayload(details)
.setHeader("foo", "baz")
.build();
};
}
Of course, I would need to put this on its own executor to avoid tying up the thread, but it doesn't work anyway - it throws a class cast exception.
error occurred during processing message in 'MethodInvokingMessageProcessor' [org.springframework.integration.handler.MethodInvokingMessageProcessor#1651b34e]; nested exception is java.lang.ClassCastException: reactor.core.publisher.MonoOnAssembly cannot be cast to org.springframework.messaging.Message, failedMessage=GenericMessage [payload=Mono.flatMap
This seems to be because the Transformer is evaluated on assembly.
Using a FluxMessageChannel seems strange, since the source is not reactive itself, but it allows me to use a service activator transform the payload.
#Bean
#ServiceActivator(inputChannel = "fromR2dbcChannel", reactive = #Reactive("publishMessageWithString"))
public MessageHandler toValidator() {
return message -> validationChannel().send(message);
}
#Bean
public Function<Flux<Message<Mono<Event>>>, Flux<Message<String>>> publishMessageWithString() {
return flux -> flux
.as(toMessageWithString());
}
private Function<Flux<Message<Mono<Event>>>, Flux<Message<String>>> toMessageWithString() {
return messageFlux -> messageFlux.map(message -> {
final Mono<Event> payload = message.getPayload();
if (payload != null) {
final Event event = payload.block();
if (event != null) {
return MessageBuilder
.withPayload(event.getDetails())
.setHeader("foo", "baz")
.build();
}
}
return null;
});
}
In real life, the message handler attached to the service activator does not understand flux natively. It's a KinesisMessageHandler.
While this seems to work, it feels awkward. Is there a better way to transform that's reactor friendly? Or an adapter that subscribes and invokes a handler? It seems very reasonable to pass Message<Mono<Event>> end to end, so the latter may not be appropriate. Thanks!
UPDATE
Thanks to Artem, a working test follows:
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import io.r2dbc.h2.H2ConnectionConfiguration;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.spi.ConnectionFactory;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.dialect.H2Dialect;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.FluxMessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.r2dbc.inbound.R2dbcMessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
#Slf4j
#SpringJUnitConfig
#DirtiesContext
public class R2dbcTest {
#Autowired
DatabaseClient client;
R2dbcEntityTemplate entityTemplate;
#Autowired
QueueChannel validationChannel;
#Autowired
FluxMessageChannel fluxChannel;
#BeforeEach
public void setup() {
this.entityTemplate = new R2dbcEntityTemplate(this.client, H2Dialect.INSTANCE);
List<String> statements =
Arrays.asList(
"DROP TABLE IF EXISTS events;",
"CREATE TABLE events (id INT AUTO_INCREMENT NOT NULL, details VARCHAR2 NOT NULL, timestamp TIMESTAMP NOT NULL);");
statements.forEach(it -> this.client.sql(it)
.fetch()
.rowsUpdated()
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete());
}
#Test
public void validateSuccessfulIntegrationFlow() throws InterruptedException {
this.entityTemplate.insert(new Event(Instant.now(), "Event details"))
.then()
.as(StepVerifier::create)
.verifyComplete();
// Validate string
final Message<?> message = validationChannel.receive();
assertThat(message.getPayload()).isEqualTo("Event details");
assertThat(message.getHeaders()).containsKey("foo");
}
#Import(R2dbcDatabaseConfiguration.class)
#Configuration
#EnableIntegration
static class SpringIntegrationConfiguration {
#Autowired
R2dbcEntityTemplate r2dbcEntityTemplate;
#Bean
FluxMessageChannel fromR2dbcChannel() {
return new FluxMessageChannel();
}
#BridgeTo(value = "validationChannel")
#Bean
FluxMessageChannel fluxChannel() {
return new FluxMessageChannel();
}
#Bean
QueueChannel validationChannel() {
return new QueueChannel();
}
#ServiceActivator(inputChannel = "fromR2dbcChannel", outputChannel = "fluxChannel", async = "true")
public Mono<Message<String>> transformEvent(Mono<Event> eventMono) {
return eventMono.map(event ->
MessageBuilder
.withPayload(event.getDetails())
.setHeader("foo", "baz")
.build());
}
// Cron expression is only here because Spring environment is fully initialized before test
// creates table, so wait for the test to start.
#Bean
#InboundChannelAdapter(value = "fromR2dbcChannel", poller = #Poller(cron = "30/2 * * * * *"))
public R2dbcMessageSource r2dbcMessageSourceSelectOne(R2dbcEntityTemplate r2dbcEntityTemplate) {
R2dbcMessageSource r2dbcMessageSource = new R2dbcMessageSource(r2dbcEntityTemplate,
"SELECT * FROM events LIMIT 1");
r2dbcMessageSource.setPayloadType(Event.class);
r2dbcMessageSource.setExpectSingleResult(true);
return r2dbcMessageSource;
}
}
#Configuration
#EnableR2dbcRepositories(basePackages = "org.springframework.integration.r2dbc.repository")
static class R2dbcDatabaseConfiguration extends AbstractR2dbcConfiguration {
#Bean
#Override
public ConnectionFactory connectionFactory() {
return createConnectionFactory();
}
public ConnectionFactory createConnectionFactory() {
return new H2ConnectionFactory(H2ConnectionConfiguration.builder()
.inMemory("r2dbc")
.username("sa")
.password("")
.option("DB_CLOSE_DELAY=-1").build());
}
#Bean
public DatabaseClient databaseClient(ConnectionFactory connectionFactory) {
return DatabaseClient.create(connectionFactory);
}
#Bean
public R2dbcEntityTemplate r2dbcEntityTemplate(DatabaseClient databaseClient) {
return new R2dbcEntityTemplate(databaseClient, H2Dialect.INSTANCE);
}
}
#Table("events")
#Getter
#Setter
#RequiredArgsConstructor
static class Event {
#Id
private Integer id;
#NonNull
public Instant timestamp;
#NonNull
public String details;
}
}
I would advice to re-think your vision about reactive stream and start avoiding calling that .block() manually.
Looking to your whole flow requirements, it is really better to make that fromR2dbcChannel as a FluxMessageChannel, so your Mono from R2DBC is going to be subscribed and processed smoothly internally by the framework if there is data.
Your #Transformer(inputChannel = "fromR2dbcChannel", outputChannel = "fromTransformer") then could just deal with a plain Event as an input parameter. Then your KinesisMessageHandler is good to deal with whatever you send to its input channel in the palyoad from that event.getDetails().
UPDATE
So, my bad. Independently of the channel for #InboundChannelAdapter, it still going to produce a Mono in the payload of the message. From here the channel type really doesn't matter. |But at the same time you can make that validationChannel as a FluxMessageChannel and then your transformer must be changed to the service activator:
#ServiceActivator(inputChannel = "fromR2dbcChannel", outputChannel = "validationChannel", async = "true")
public Mono<Message<String>> transformEvent(Mono<Event> eventMono) {
return eventMono.map(
MessageBuilder
.withPayload(event.getDetails())
.setHeader("foo", "baz")
.build());
}
This way the result Mono is going to be subscribed by the FluxMessageChannel and the rest of your "blocking" flow should remain the same.
The problem with transformer that it does return the Message if result of POJO method is not a Message.
One of the interactions I want to test is that a class Foo is supposed to pass a Stream<Changes> to FooListener.someChangesHappened. Is there a Mockito idiom to verify that a stream contained the expected objects?
Assuming you are just verifying the argument to a mock implementation, and are not actually using it, here is a custom Hamcrest Matcher that will get the job done. It gets hairy when you need to read from the Stream more than once, because Streams are not built for that. You'll notice that this solution even needs to protect itself from JUnit calling matches more than once.
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.verify;
#RunWith(MockitoJUnitRunner.class)
public class Foo {
#Mock
FooListener fooListener;
#Before
public void happen() {
fooListener.someChangesHappened(Stream.of(Changes.ONE, Changes.TWO, Changes.THREE));
}
#Test
public void contains() {
verify(fooListener).someChangesHappened(argThat(streamThat(hasItem(Changes.TWO))));
}
#Test
public void doesNotContain() {
verify(fooListener).someChangesHappened(argThat(streamThat(not(hasItem(Changes.FOUR)))));
}
private static <T> Matcher<Stream<T>> streamThat(Matcher<Iterable<? super T>> toMatch) {
return new IterableStream<>(toMatch);
}
private interface FooListener {
void someChangesHappened(Stream<Changes> stream);
}
private enum Changes {
ONE, TWO, THREE, FOUR
}
private static class IterableStream<T> extends TypeSafeMatcher<Stream<T>> {
Matcher<Iterable<? super T>> toMatch;
List<T> input = null;
public IterableStream(Matcher<Iterable<? super T>> toMatch) {
this.toMatch = toMatch;
}
#Override
protected synchronized boolean matchesSafely(Stream<T> item) {
// This is to protect against JUnit calling this more than once
input = input == null ? item.collect(Collectors.toList()) : input;
return toMatch.matches(input);
}
#Override
public void describeTo(Description description) {
description.appendText("stream that represents ");
toMatch.describeTo(description);
}
}
}
I compile single groovy source module "in fly" using GroovyClassLoader.parseClass(src) and all is ok.
But problem is when this source module imports other classes, these are not compiled yet. Traditional compiling when I start compilation of one source but other are required and ready on source path, are compiled too.
How can I use GroovyClassLoader with target to compile all other required sources NOT FROM FILESYSYSTEM.
My sources are for example in database, remote http via URI etc.
The key is to make custom URL handling -- you have to implement a URLStreamHandler and a URLConnection.
If you google around, there's some good documentation on how to implement the stream handler and connection classes -- but for what you're doing, you really only need dummy implementations.
Here's some source code to bootstrap you -- it demonstrates how to connect the pieces up. If you provide some implementation of lookupScriptCodeWithJDBCorWhatever you'll be good to go.
import groovy.lang.GroovyResourceLoader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public class CustomGroovyResourceLoader implements GroovyResourceLoader {
private final GroovyResourceLoader parentLoader;
public CustomGroovyResourceLoader(GroovyResourceLoader parentLoader) {
this.parentLoader = parentLoader;
}
#Override
public URL loadGroovySource(String filename) throws MalformedURLException {
URL resourceURL = null;
if (parentLoader != null) {
resourceURL = parentLoader.loadGroovySource(filename);
}
if (resourceURL == null) {
resourceURL = createURL(filename);
}
return resourceURL;
}
public URL createURL(String resourceName) throws MalformedURLException {
String scriptSourceCode = lookupScriptCodeWithJDBCorWhatever(resourceName);
return new URL(null, "groovy:///" + resourceName,
new GroovyResourceStreamHandler(scriptSourceCode));
}
private String lookupScriptCodeWithJDBCorWhatever(String resourceName) {
//TODO: blah blah blah
return null;
}
class GroovyResourceConnection extends URLConnection {
private final String urlData;
protected GroovyResourceConnection(URL url, String logic) {
super(url);
this.urlData = logic;
}
#Override
public void connect() throws IOException {}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(urlData.getBytes());
}
}
class GroovyResourceStreamHandler extends URLStreamHandler {
private final String scriptSource;
public GroovyResourceStreamHandler(String scriptSource) {
this.scriptSource = scriptSource;
}
#Override
protected URLConnection openConnection(URL u) throws IOException {
GroovyResourceConnection connection = new GroovyResourceConnection(u, scriptSource);
return connection;
}
}
}
You then install this thing with some code that looks like this:
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
groovyClassLoader.setResourceLoader( new CustomGroovyResourceLoader( groovyClassLoader.getResourceLoader() ) );
This is a followup to my previous post at ToolTip Performance in XPages I have got the code to do it written (not tested) so I can't seem to get my Managed Bean to get called properly. My config contians the following:
<managed-bean id="ToolTip">
<managed-bean-name>WFSToolTip</managed-bean-name>
<managed-bean-class>ca.workflo.wfsToolTip.ToolTipText</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
and I have stripped my code down to the bare minimum:
package ca.workflo.wfsToolTip;
public class ToolTipText {
public String getToolTipText(String key){
return key;
}
}
My class is in the build path. I have a simple XPage with one filed on it and a tool tip for that field. The code for the tooltip is:
<xe:tooltip id="tooltip1" for="inputText1">
<xe:this.label>
<![CDATA[#{javascript:WFSToolTip.getToolTipText("More Stuff");}]]>
</xe:this.label>
</xe:tooltip>
When I load the test XPage in the browser I get an error that:
Error while executing JavaScript computed expression
Script interpreter error, line=1, col=12: Error calling method 'getToolTipText(string)' on java class 'ca.workflo.wfsToolTip.ToolTipText'
JavaScript code
1: WFSToolTip.getToolTipText("More Stuff");
I can't figure out why the call to getToolTipText would fail.
Can anyone see where I'm going wrong. This is my first Managed Bean and at the moment it is managing me rather than the other way around.
Thanks.
You need to:
- implement Serializable which boils down to state it and provide a version
- implement Map ... a little more work
Then you use Expression Language instead of SSJS. It would look like #{WFSToolTip["More Stuff"]}
This is how such a class would look like. You need to:
adjust the view name to reflect the name you want
the view needs to be flat, column 1 = tooltip name, column 2 = tooltip text
somewhere (on an admin/config page) you need to call WFSToolTip.clear(); (in SSJS) after you update the values in the configuration.
The example doesn't lazyload since running though a view navigator once is really fast. No point to do all these lookups.
Here you go:
package com.notessensei.xpages;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import lotus.domino.Base;
import lotus.domino.Database;
import lotus.domino.NotesException;
import lotus.domino.View;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import com.ibm.xsp.extlib.util.ExtLibUtil;
public class Parameters implements Serializable, Map<String, String> {
private final static String CONFIG_VIEW = "keywords";
private static final long serialVersionUID = 1L;
private final Map<String, String> internalMap = new HashMap<String, String>();
public Parameters() {
this.populateParameters(internalMap);
}
private void populateParameters(Map<String, String> theMap) {
Database d = ExtLibUtil.getCurrentDatabase();
try {
View v = d.getView(CONFIG_VIEW);
ViewEntryCollection vec = v.getAllEntries();
ViewEntry ve = vec.getFirstEntry();
ViewEntry nextVe = null;
while (ve != null) {
nextVe = vec.getNextEntry(ve);
// Load the parameters, column 0 is the key, column 0 the value
Vector colVal = ve.getColumnValues();
theMap.put(colVal.get(0).toString(), colVal.get(1).toString());
// Cleanup
this.shred(ve);
ve = nextVe;
}
// recycle, but not the current database!!!
this.shred(ve, nextVe, vec, v);
} catch (NotesException e) {
e.printStackTrace();
}
}
public void clear() {
this.internalMap.clear();
this.populateParameters(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, String>> entrySet() {
return this.internalMap.entrySet();
}
public String get(Object key) {
return this.internalMap.get(key);
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public String put(String key, String value) {
return this.internalMap.put(key, value);
}
public void putAll(Map<? extends String, ? extends String> m) {
this.internalMap.putAll(m);
}
public String remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<String> values() {
return this.internalMap.values();
}
private void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}
}
The difference to a regular HashMap is only the constructor that populates it. Hope that clarifies it.
I've never seen that id property.. My beans in faces-config look like this:
<managed-bean>
<managed-bean-name>CurrentJob</managed-bean-name>
<managed-bean-class>com.domain.inventory.Job</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Technically managed beans should implement Serializable and have a blank constructor. So you should have something like this inside :
public ToolTipText() {
}
I THINK you can get away without the Serializable for somethings... I always implement though but I'm sure you need the no argument constructor.
Thanks to all that have responded and helped out here especially Stephan Wissel. I thought I would post my version of Stephan's code, pretty much the same. There are issues with making the class an ApplicationScope because you need to shut down the HTTP task to refresh and reload the Class. What I did was added a button to the custom control where I to the view of the tooltips where I do the CRUD stuff and in the button do WFSToolTip().clear() and it rebuilds the map. Pretty neat. My next task for this is try to do the CRUD using JAVA and update the map directly. At the moment though I need to move on to my next task.
My next task revolves around a very similar Class. I have a master database that contains all the basic design and code. Then I have one or more applications that use that code and store the documents in their own database that contains the forms and views for that specific application. In the master I have created one or more application documents. Each of these documents contains the AppName (the key value) then the Map value is an array (Vector) containing the ReplicaID of the Application Database and a few other pieces of information. My class the loads a Map entry for each Application and collects a bunch of other information about the application from several places and stores that in the Map Value. At this point then I can set Database db = thisClass.getDatabase("App Name"). so a single custom control can be used for any/all of the applications. Pretty cool. I think I could get to like this.
Anyway here is the code I'm using for the ToolTips - BTW It has taken an XPage with about 175 fields and 100+ tooltips from being painfully slow to being acceptable. The good thing about it is that the XPage is creating a process profile document and once created it is not frequently modified as an admin action - not an everyday user action.
Please feel free point out error, omitions or suggestions to the code:
package ca.workflo.wfsToolTip;
import lotus.domino.Base;
import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import com.ibm.xsp.extlib.util.ExtLibUtil;
public class ToolTipText implements Serializable, Map<String, String> {
private static final long serialVersionUID = 1L;
private Session s;
private String repID;
private Database db;
private Database helpDB;
private View helpView;
private ViewEntry ve;
private ViewEntry tVE;
private ViewEntryCollection veCol;
private final Map<String, String> internalMap = new HashMap<String, String>();
public ToolTipText() {
this.populateMap(internalMap);
}
private void populateMap(Map<String, String> theMap) {
try {
s = ExtLibUtil.getCurrentSession();
db = s.getCurrentDatabase();
repID = db.getProfileDocument("frmConfigProfile", "").getItemValue(
"WFSHelpRepID").firstElement().toString();
helpDB = s.getDbDirectory(null).openDatabaseByReplicaID(repID);
helpView = helpDB.getView("vwWFSToolTipHelp");
veCol = helpView.getAllEntries();
ve = veCol.getFirstEntry();
ViewEntry tVE = null;
while (ve != null) {
tVE = veCol.getNextEntry(ve);
Vector colVal = ve.getColumnValues();
theMap.put(colVal.get(0).toString(), colVal.get(1).toString());
recycleObjects(ve);
ve = tVE;
}
} catch (NotesException e) {
System.out.println(e.toString());
}finally{
recycleObjects(ve, tVE, veCol, helpView, helpDB);
}
}
public void clear() {
this.internalMap.clear();
this.populateMap(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, String>> entrySet() {
return this.internalMap.entrySet();
}
public String get(Object key) {
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
} else {
return "There is no Tooltip Help for " + key;
}
} catch (Exception e) {
return "error in tooltip get Object ";
}
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public String put(String key, String value) {
return this.internalMap.put(key, value);
}
public void putAll(Map<? extends String, ? extends String> m) {
this.internalMap.putAll(m);
}
public String remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<String> values() {
return this.internalMap.values();
}
public static void recycleObjects(Object... args) {
for (Object o : args) {
if (o != null) {
if (o instanceof Base) {
try {
((Base) o).recycle();
} catch (Throwable t) {
// who cares?
}
}
}
}
}
}
I am trying to create an action in which the server needs to response an array list of objects over the wire to the client through GWTP Action.
Category class
package com.business.share;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
public class Category implements Serializable{
Long id;
protected String name;
protected String description;
protected boolean status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean getStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
}
GetCategories class
package com.business.client.action;
import java.util.ArrayList;
import com.gwtplatform.dispatch.shared.ActionImpl;
import com.business.client.action.GetCategoriesResult;
import com.business.share.Category;
public class GetCategories extends ActionImpl<GetCategoriesResult> {
private ArrayList<Category> categories;
#SuppressWarnings("unused")
public GetCategories() {
// For serialization only
}
public GetCategories(ArrayList<Category> categories) {
this.categories = categories;
}
public ArrayList<Category> getCategories() {
return categories;
}
}
GetCategoriesResult class
package com.business.client.action;
import java.util.ArrayList;
import com.gwtplatform.dispatch.shared.Result;
import com.business.share.Category;
public class GetCategoriesResult implements Result {
private ArrayList<Category> categories;
#SuppressWarnings("unused")
private GetCategoriesResult() {
// For serialization only
}
public GetCategoriesResult(ArrayList<Category> categories) {
this.categories = categories;
}
public ArrayList<Category> getCategories() {
return categories;
}
}
GetCategoriesActionHandler class
package com.business.server.handler;
import java.util.ArrayList;
import com.gwtplatform.dispatch.server.actionhandler.ActionHandler;
import com.business.client.action.GetCategories;
import com.business.client.action.GetCategoriesResult;
import com.business.share.Category;
import com.google.inject.Inject;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;
import com.gwtplatform.dispatch.server.ExecutionContext;
import com.gwtplatform.dispatch.shared.ActionException;
public class GetCategoriesActionHandler implements
ActionHandler<GetCategories, GetCategoriesResult> {
#Inject
public GetCategoriesActionHandler() {
}
#Override
public GetCategoriesResult execute(GetCategories action,
ExecutionContext context) throws ActionException {
ArrayList<Category> categories = new ArrayList<Category>();
// dummy data
Category cat1 = new Category();
cat1.setName("cat1");
cat1.setDescription("cat1 desc");
cat1.setStatus(true);
Category cat2 = new Category();
cat1.setName("cat2");
cat1.setDescription("cat2 desc");
cat1.setStatus(false);
categories.add(cat1);
categories.add(cat2);
return new GetCategoriesResult(categories);
}
#Override
public void undo(GetCategories action, GetCategoriesResult result,
ExecutionContext context) throws ActionException {
}
#Override
public Class<GetCategories> getActionType() {
return GetCategories.class;
}
}
And this is a piece of code in CategoryPresenter, which sends async to server.
#Override
protected void onReset() {
super.onReset();
GetCategories getCategoriesAction = new GetCategories();
dispatchAsync.execute(getCategoriesAction, getCategoriesCallback);
}
private final AsyncCallback<GetCategoriesResult> getCategoriesCallback =
new AsyncCallback<GetCategoriesResult>() {
#Override
public void onFailure(Throwable caught) {
}
#Override
public void onSuccess(GetCategoriesResult result) {
getView().getCategoryListBox().clear();
ArrayList<Category> categories = result.getCategories();
for(Category category : categories) {
getView().getCategoryListBox().addItem(category.getName());
}
}
};
I don't know what wrong with this piece of code, but GWT compiler always gives error like this.
Compiling module com.business.Business
Validating newly compiled units
Ignored 3 units with compilation errors in first pass.
Compile with -strict or with -logLevel set to TRACE or DEBUG to see all errors.
Finding entry point classes
[ERROR] Errors in 'file:/.blah..blah..blah../businessapp/src/com/business/client/presenter/CategoryPresenter.java'
[ERROR] Line 75: No source code is available for type com.business.share.Category; did you forget to inherit a required module?
[ERROR] Errors in 'file:/.blah..blah..blah../businessapp/src/com/business/client/action/GetCategoriesResult.java'
[ERROR] Line 11: No source code is available for type com.business.share.Category; did you forget to inherit a required module?
[ERROR] Unable to find type 'com.business.client.Business'
[ERROR] Hint: Previous compiler errors may have made this type unavailable
[ERROR] Hint: Check the inheritance chain from your module; it may not be inheriting a required module or a module may not be adding its source path entries properly
Following this error message, it means, com.business.share.Category is not found, but this file is physically stored in that package already. I don't understand why GWT could not find it. I noticed anywhere that I make call Category class, it brings this error always.
Somebody's got an idea on what's going on?
[EDIT]
The problem is solved.
In my Business.gwt.xml, I have
<source path='shared'/>
But my share package is com.business.share (without d)
I just rename the package name from share to shared.
Try to add an empty constructor to the Category class.