Request Factory GWT editor change isn't persisting related JDO entities - google-app-engine

I'm using (and new to) RequestFactory in GWT 2.5, with JDO entities with a one-to-many relationship, on AppEngine datastore. I've just started using the GWT RequestFactoryEditorDriver to display/edit my objects.
The Driver traverses my objects fine, and displays them correctly. However, when I try to edit a value on the "related" objects, the change doesn't get persisted to the datastore.
When I change b.name on my UI and click "save", I notice only A's persist() call is called. B's persist() is never called. How do I make the editorDriver fire on both ARequest as well as BRequest request contexts? (since what I want is for B's InstanceRequest<AProxy,Void> persist() to be called when my edits are to B objects only.)
Also, AFAICT, if I have an editor on BProxy, any object b that is being shown by the editor (and following the Editor Contract) should automatically be "context.edit(b)"ed by the Driver to make it mutable. However, in my case "context" is an ARequest, not a BRequest.
Do I have to make a ValueAwareEditor like mentioned here: GWT Editor framework
and create a fresh BRequest inside the flush() call and fire it, so that changes to B separately persist in a BRequest before the ARequest is fired?
editorDriver.getPaths() gives me:
"bs"
Also, the driver definitely sees the change to B's property, as editorDriver.isChanged() returns true before I fire() the context.
There are no errors on my client-side or server-side logs, and the Annotation Processor runs with no warnings.
Here's how I setup my driver:
editorDriver = GWT.create(Driver.class);
editorDriver.initialize(rf, view.getAEditor());
final ARequest aRequest = rf.ARequest();
final Request<List<AProxy>> aRequest = aRequest.findAByUser(loginInfo.getUserId());
String[] paths = editorDriver.getPaths();
aRequest.with(paths).fire(new Receiver<List<AProxy>>() {
#Override
public void onSuccess(List<AProxy> response) {
AProxy a = response.get(0);
ARequest aRequest2 = rf.aRequest();
editorDriver.edit(a, aRequest2);
aRequest2.persist().using(a);
}
});
This is how my entities look:
public abstract class PersistentEntity {
public Void persist() {
PersistenceManager pm = getPersistenceManager();
try {
pm.makePersistent(this);
} finally {
pm.close();
}
return null;
}
public Void remove() {
PersistenceManager pm = getPersistenceManager();
try {
pm.deletePersistent(this);
} finally {
pm.close();
}
return null;
}
}
#PersistenceCapable(identityType = IdentityType.APPLICATION)
#Version(strategy=VersionStrategy.VERSION_NUMBER, column="VERSION",
extensions={#Extension(vendorName="datanucleus", key="field-name", value="version")})
public class A extends PersistentEntity {
... (Id, version omitted for brevity)
#Persistent
private String name;
#Persistent
private List<B> bs;
public String getName() {
return name;
}
...
public void setName(String name) {
this.name = name;
}
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
}
... (same annotations as above omitted for brevity)
public class B extends PersistentEntity {
... (Id, version omitted for brevity)
#Persistent
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Here are the proxies:
#ProxyFor(A.class)
public interface AProxy extends EntityProxy {
String getName();
List<BProxy> getBs();
void setName(String name);
void setBs(List<BProxy> bs);
}
#ProxyFor(B.class)
public interface BProxy extends EntityProxy {
String getName();
void setName(String name);
}
Here are my service stubs:
#Service(A.class)
public interface ARequest extends RequestContext {
Request<List<A>> findAByUser(String userId);
InstanceRequest<AProxy, Void> persist();
InstanceRequest<AProxy, Void> remove();
}
#Service(B.class)
public interface BRequest extends RequestContext {
Request<List<A>> findB(String key);
InstanceRequest<BProxy, Void> persist();
InstanceRequest<BProxy, Void> remove();
}
Edit:
I've now changed my ARequest interface and service implementation to support a "saveAndReturn" method, so that I can recursively "persist" "a" on the server side:
Request<UserSandboxProxy> saveAndReturn(AProxy aProxy);
I find now that when I "flush" my RequestFactoryEditorDriver, the client-side context object has my new "b.name" value. However, if I call "context.fire()" and inspect my "saveAndReturn" method on the server side, the resulting server-side object "a", just before I "persist" it, doesn't contain the change to "b.name" on any item of the List.
Why could this be happening? How do I debug why this client-information doesn't go across the wire, to the server?
Options I've considered, tried and ruled out:
1) Ensuring the APT has been run, and there are no warnings/errors on Proxy or Service interfaces
2) Ensuring that my proxies does have a valid setter in AProxy for the List

You have to use a session-per-request pattern for RequestFactory to work properly. More details here: https://code.google.com/p/google-web-toolkit/issues/detail?id=7827

Related

Reactive Panache relation JsonMappingException: Collection cannot be initialized

The non-reactive version of this code works fine. But in the reactive version, something happens when mapping a null or empty collection from the database.
The POST of a new Template object returns a 201 with nothing unusual in the logs. But, when I do the GET on Template, the listAll() returns the error below.
I've tried initializing the "sections" member to an empty collection, but the result is the same.
What am I missing?
The Reactive Entity:
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
#Entity
public class Template extends PanacheEntity {
public String name;
#OneToMany(mappedBy = "template", cascade = CascadeType.ALL)
public List<Section> sections;
}
The Resource API:
#GET
#Path("template")
public Uni<List<Template>> listTemplates() {
return Template.<Template>listAll();
}
#POST
#Path("template")
#Consumes("application/json")
#Produces("application/json")
#ReactiveTransactional
public Uni<Response> addTemplate(Template template) {
return Panache.<Template>withTransaction(template::persist)
.onItem().transform(inserted -> {
return createdResponse("/template/%d", inserted.id);
});
}
The Dependencies:
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
<artifactId>quarkus-resteasy-reactive</artifactId>
<artifactId>quarkus-reactive-pg-client</artifactId>
The error:
JsonMappingException: HR000056: Collection cannot be initialized: score.Template.sections (through reference chain: java.util.ArrayList[0]->score.Template["sections"])
try this workaround fetch = FetchType.EAGER
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
#Entity
public class Template extends PanacheEntity {
public String name;
#OneToMany(mappedBy = "template", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
public List<Section> sections;
}

MappingException: Ambiguous field mapping detected

Using Spring boot 1.5.6.RELEASE.
I have the following mongo document base class:
#Document(collection="validation_commercial")
public abstract class Tier {
#Id
private String id;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date created;
#Field("tran")
private Tran tran;
public Tier() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Tran getTran() {
return tran;
}
public void setTran(Tran tran) {
this.tran = tran;
}
}
which is then extended:
public class Tier1 extends Tier {
#Field("tier1")
private Tier1Programs tier1;
public Tier1() {
this.tier1 = new Tier1Programs();
}
public Tier1Programs getTier1() {
return tier1;
}
public void setTier1(Tier1Programs tier1) {
this.tier1 = tier1;
}
}
which in turn is extended:
public class Tier2 extends Tier1 {
#Field("tier2")
private Tier2Programs tier2;
public Tier2() {
this.tier2 = new Tier2Programs();
}
public Tier2Programs getTier2() {
return tier2;
}
public void setTier2(Tier2Programs tier2) {
this.tier2 = tier2;
}
}
There is a Tier1 Supervisor (Spring Boot Application) that uses the Tier1 class within the MongoRepository interface:
public interface Tier1Repository extends MongoRepository<Tier1,String>{}
for retrieving and saving - no issue.
I then have a Tier2 Supervisor (Spring Boot Application) that uses a Tier1 Repository (for retrieving the Tier1 document and a Tier2 Repository for saving the Tier2 document:
#Repository("tier1Repository")
public interface Tier1Repository extends MongoRepository<Tier1,String>{}
#Repository("tier2Repository")
public interface Tier2Repository extends MongoRepository<Tier2,String>{}
My service is:
#Service
public class TierService {
#Qualifier("tier1Repository")
#Autowired
private final Tier1Repository tier1Repository;
#Qualifier("tier2Repository")
#Autowired
private final Tier2Repository tier2Repository;
public TierService(#Qualifier("tier1Repository") Tier1Repository tier1Repository, #Qualifier("tier2Repository") Tier2Repository tier2Repository) {
this.tier1Repository = tier1Repository;
this.tier2Repository = tier2Repository;
}
public Tier1 findOne(String id) {
return tier1Repository.findOne(id);
}
public void SaveTier(Tier2 tier) {
tier2Repository.save(tier);
}
public Tier1Repository getTier1Repository() {
return tier1Repository;
}
public Tier2Repository getTier2Repository() {
return tier2Repository;
}
}
and finally the app:
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class})
#Configuration
#ComponentScan(basePackages = {"com.k12commercial.tier2supervisor"})
#ImportResource("classpath:application-context.xml")
public class Application implements CommandLineRunner {
#Autowired
private IReceiver raBidNetPriceReceiver;
#Autowired
private UdyDataSourceFactory udyDSRegistry;
public static void main(String[] args) throws InterruptedException {
try {
SpringApplication.run(Application.class, args);
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void run(String... args) throws Exception {
raBidNetPriceReceiver.processTierMessages();
exit(0);
}
}
When I run the Tier2 Supervisor from the command line I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'tierService' defined in URL
[jar:file:/opt/java-commandline/tier2supervisor-1.0.jar!/BOOT-INF/classes!/com/k12commercial/tier2supervisor/service/TierService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tier2Repository': Invocation of init method failed; nested exception is org.springframework.data.mapping.model.MappingException: Ambiguous field mapping detected! Both private final java.lang.reflect.Type org.springframework.data.util.TypeDiscoverer.type and private final java.lang.Class org.springframework.data.util.ClassTypeInformation.type map to the same field name type! Disambiguate using #Field annotation!
I am not sure if the issue is Tier2 extending Tier1 (did try putting #Document tag above Tier1 and Tier2 with no change). I think I have marked the relevant fields so don't understand the need to disambiguate. I thought the issue was having 2 repositories (Spring Boot not knowing which one to DI) so removed the Tier1Repository - didn't work. Tried better qualifying the repositories but still got the same error. I made Tier1 and Tier2 #Transient and that got rid of the message but also removed the tier1 section in the mongo document - so wrong correction.
Thinking it is an annotation fix but not seeing it...
Please advise - thank you.
Sorry for the delay (I got pulled away to work on something else) and thank you to those who responded.
The issue was I had a MongoTemplate in my Tier level programs e.g.Tier2Programs (sub library) which Spring Boot was trying to autowire.
By moving the Mongo (CRUD) requirements to the supervisor level (I also replaced the Repositories with one MongoTemplate to simplify) I removed the ambiguity. (I also removed the Service class).
The code is contained with the RaBidNetReciever class
#Component
public class RaBidNetPriceReceiver extends BaseReceiver implements IReceiver, ApplicationEventPublisherAware {
private static final Logger LOGGER = LoggerFactory.getLogger(RaBidNetPriceReceiver.class);
private final RabbitTemplate raBidNetPriceRabbitTemplate;
public RaBidNetPriceReceiver(MongoTemplate mongoTemplate, RabbitTemplate raBidNetPriceRabbitTemplate) {
super(mongoTemplate);
this.raBidNetPriceRabbitTemplate = raBidNetPriceRabbitTemplate;
}
#Transactional
public void processTierMessages() {
try {
while (true) {
gson = getGsonBuilder().create();
byte[] body = (byte[]) raBidNetPriceRabbitTemplate.receiveAndConvert();
if (body == null) {
setFinished(true);
break;
}
tier1Message = gson.fromJson(new String(body), Tier1Message.class);
// document a 'Tier1' type so retrieve Tier1 first...
Tier1 tier1 = mongoTemplate.findById(tier1Message.getId(), Tier1.class);
Tier2Message tier2Message = new Tier2Message(tier1Message.getTran(), tier1Message.getId());
Tier2Process tierProcess = getTierProcess(tier2Message.getTran().getK12ArchitectureId());
Tier2 tier2 = new Tier2();
tier2.setId(tier1.getId());
tier2.setTier1Programs(tier1.getTier1Programs());
tier2.setCreated(tier1.getCreated());
tier2.setTran(tier1.getTran());
tierProcess.setTier(tier2);
tier2 = tier2.getTier2Programs().getRaBidNetPriceProgram().process(tierProcess);
mongoTemplate.save(tier2);
if (tier2.getTier2Programs().getRaBidNetPriceProgram().isFinished()) {
// publish event
publisher.publishEvent(new ProgramEvent(this, "FINISHED", tier2Message));
}
}
} catch (Exception e) {
LOGGER.error("id: " + tier1Message.getId() + " " + e.getMessage());
}
}
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
Thank you,

Objectify Ref<> with #Load is not working with ofy().load().group()

I have a problem Ref<> usage with #Load. Basically I made a copy paste from Objectify website to test #Load annotation with Load Groups.
#Entity
public static class Thing {
public static class Partial {}
public static class Everything extends Partial {}
public static class Stopper {}
#Id Long id;
#Load(Partial.class) Ref<Other> withPartial;
#Load(Everything.class) Ref<Other> withEveryhthing;
#Load(unless=Stopper.class) Ref<Other> unlessStopper;
public Ref<Other> getWithPartial() {
return withPartial;
}
public void setWithPartial(Ref<Other> withPartial) {
this.withPartial = withPartial;
}
public Ref<Other> getWithEveryhthing() {
return withEveryhthing;
}
public void setWithEveryhthing(Ref<Other> withEveryhthing) {
this.withEveryhthing = withEveryhthing;
}
public Ref<Other> getUnlessStopper() {
return unlessStopper;
}
public void setUnlessStopper(Ref<Other> unlessStopper) {
this.unlessStopper = unlessStopper;
}
}
Then I wrote the following code.
Other other = new Other();
Key<Other> otherKey = ofy().save().entity(other).now();
Thing thing = new Thing();
thing.setWithPartial(Ref.create(otherKey));
Key<Thing> thingKey = ofy().save().entity(thing).now();
Thing t = ofy().load().key(thingKey).now();
System.out.println("Is loaded: " + t.getWithPartial().isLoaded());
Without writing ofy().load().group(Partial.class).key(thingKey).now(); other entity still loads into session. However in documentation it needs group class to be loaded.
The isLoaded() method tests whether the entity is in the session cache. Since you just save()d the entity, it's already in the session cache.
If you want to test the behavior of load groups, you need to ofy().clear() the cache.

testng how to dynamically set groups from Factory?

Before I setup a test class like the code below:
1. the Factory and test Dataprovider both used excel as the dataprovider.
2. In the Factory dataprovider table, it has a list of url
3. Each time, it will find one of the url in the factory dataprovider table, and run the test in each test methods..
public class Test {
WebDriver driver;
private String hostName;
private String url;
#Factory(dataProvider = "xxxx global variables", dataProviderClass = xxxx.class)
public GetVariables(String hostName, String url) {
this.hostName = hostName;
this.url = url;
}
#BeforeMethod
#Parameters("browser")
public void start(String browser) throws Exception {
driver = new FirefoxDriver();
driver.get(url);
Thread.sleep(1000);
}
#Test(priority = 10, dataProvider = "dataprovider Test A", dataProviderClass = xxx.class)
public void TestA(Variable1,
Variable2,Variable3) throws Exception {
some test here...
}
#Test(priority = 20, dataProvider = "dataprovider Test B", dataProviderClass = xxx.class)
public void TestB(Variable1,
Variable2,Variable3)
throws Exception {
some test here...
}
#AfterMethod
public void tearDown() {
driver.quit();
}
Now I want to dynamically assign different group for each test for different url. I am thinking add a variable 'flag' in the #Factory dataprovider:
#Factory(dataProvider = "xxxx global variables", dataProviderClass = xxxx.class)
public GetVariables(String hostName, String url, String flag) {
this.hostName = hostName;
this.url = url;
this.flag = flag;
}
That when flag.equals("A"), it will only run test cases in test groups={"A"}.
When flag.equals("B"), it will only run test cases in test groups ={"B"},
When flag.equals("A,B"), it will only run test cases in test groups ={"A","B"}
Is there any way I can do that?
Thank you!
TestNG groups provides "flexibility in how you partition your tests" but it isn't for conditional test sets. For that you simply use plain old Java.
You can use inheritance or composition (I recommend the latter, see Item 16: Favor composition over inheritance from Effective Java).
Either way the general idea is the same: use a Factory to create your test class instances dynamically creating the appropriate class type with the appropriate test annotations and/or methods that you want to run.
Examples:
Inheritance
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class DemoTest {
#Factory
public static Object[] createTests() {
return new Object[]{
new FlavorATest(),
new FlavorBTest(),
new FlavorABTest()
};
}
/**
* Base test class with code for both A-tests and B-tests.
*
* Note that none of these test methods are annotated as tests so that
* subclasses may pick which ones to annotate.
*/
public static abstract class BaseTest {
protected void testA() {
// test something specific to flavor A
}
protected void testB() {
// test something specific to flavor B
}
}
// extend base but only annotate A-tests
public static class FlavorATest extends BaseTest {
#Test
#Override
public void testA() {
super.testA();
}
}
// extend base but only annotate B-tests
public static class FlavorBTest extends BaseTest {
#Test
#Override
public void testB() {
super.testB();
}
}
// extend base and annotate both A-tests and B-tests
public static class FlavorABTest extends BaseTest {
#Test
#Override
public void testA() {
super.testA();
}
#Test
#Override
public void testB() {
super.testB();
}
}
}
Composition
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class DemoTest {
#Factory
public static Object[] createTests() {
return new Object[]{
new FlavorATest(),
new FlavorBTest(),
new FlavorABTest()
};
}
private static void testA() {
// test something specific to flavor A
}
private static void testB() {
// test something specific to flavor B
}
// only create A-test methods and delegate to shared code above
public static class FlavorATest {
#Test
public void testA() {
DemoTest.testA();
}
}
// only create B-test methods and delegate to shared code above
public static class FlavorBTest {
#Test
public void testB() {
DemoTest.testB();
}
}
// create A-test and B-test methods and delegate to shared code above
public static class FlavorABTest {
#Test
public void testA() {
DemoTest.testA();
}
#Test
public void testB() {
DemoTest.testB();
}
}
}
Your factory methods won't be as simple as you'll need to use your "flag" from your test data to switch off of and create instances of the appropriate test classes.

Suggest Addresses in a SuggestBox in GWT/Java

I want to define a SuggestBox, which behaves like the search bar in Google Maps: When you begin to type, real addresses, starting with the typed letters, appear.
I think, that I need to use the Geocoder.getLocations(String address, LocationCallback callback) method, but I have no idea how to connect this with the oracle, which is needed by the suggest box to produce its suggestions.
Can you please give me ideas how do I connect the getLocations Method with the SuggestOracle?
I solved this by implementing a subclass of SuggestBox, which has it's own SuggestOracle. The AddressOracle deals as a Wrapper for the Google Maps Service, for which the class Geocoderin the Google Maps API for GWT offers abstractions.
So here is my solution:
First we implement the Widget for a SuggestBox with Google Maps suggestions
public class GoogleMapsSuggestBox extends SuggestBox {
public GoogleMapsSuggestBox() {
super(new AddressOracle());
}
}
Then we implement the SuggestOracle, which wraps the Geocoder async method abstractions:
class AddressOracle extends SuggestOracle {
// this instance is needed, to call the getLocations-Service
private final Geocoder geocoder;
public AddressOracle() {
geocoder = new Geocoder();
}
#Override
public void requestSuggestions(final Request request,
final Callback callback) {
// this is the string, the user has typed so far
String addressQuery = request.getQuery();
// look up for suggestions, only if at least 2 letters have been typed
if (addressQuery.length() > 2) {
geocoder.getLocations(addressQuery, new LocationCallback() {
#Override
public void onFailure(int statusCode) {
// do nothing
}
#Override
public void onSuccess(JsArray<Placemark> places) {
// create an oracle response from the places, found by the
// getLocations-Service
Collection<Suggestion> result = new LinkedList<Suggestion>();
for (int i = 0; i < places.length(); i++) {
String address = places.get(i).getAddress();
AddressSuggestion newSuggestion = new AddressSuggestion(
address);
result.add(newSuggestion);
}
Response response = new Response(result);
callback.onSuggestionsReady(request, response);
}
});
} else {
Response response = new Response(
Collections.<Suggestion> emptyList());
callback.onSuggestionsReady(request, response);
}
}
}
And this is a special class for the oracle suggestions, which just represent a String with the delivered address.
class AddressSuggestion implements SuggestOracle.Suggestion, Serializable {
private static final long serialVersionUID = 1L;
String address;
public AddressSuggestion(String address) {
this.address = address;
}
#Override
public String getDisplayString() {
return this.address;
}
#Override
public String getReplacementString() {
return this.address;
}
}
Now you can bind the new widget into your web page by writing the following line in the onModuleLoad()-method of your EntryPoint-class:
RootPanel.get("hm-map").add(new GoogleMapsSuggestBox());

Resources