Several packages have been renamed to a more consistent convention under the prefix ‘org.granite.client.javafx’. Usually a simple ‘Optimize imports’ or search/replace in your IDE should be enough to do the necessary changes.
The ChannelFactory API has been slighly changed to allow different channel types:
ChannelFactory channelFactory = new JMFChannelFactory(); channelFactory.start(); MessagingChannel channel = channelFactory.newMessagingChannel("long-polling", "longPollingChannel", "http://localhost:8080/gravityamf/amf.txt") Consumer consumer = new Consumer(channel, "stocks", "europe");
For a websocket channel, you will also have to configure the transport:
ChannelFactory channelFactory = new JMFChannelFactory(); channelFactory.setMessagingTransport("websocket", new JettyWebSocketTransport()); channelFactory.start(); MessagingChannel channel = channelFactory.newMessagingChannel("websocket", "websocketChannel", "ws://localhost:8080/myapp/gravityamf/amf") Consumer consumer = new Consumer(channel, "stocks", "europe");
For a UDP channel, assuming the granite-client-java-udp.jar library is present:
ChannelFactory channelFactory = new JMFChannelFactory(); channelFactory.start(); MessagingChannel channel = channelFactory.newMessagingChannel("udp", "udpChannel", "http://localhost:8080/myapp/gravityamf/amf.txt") Consumer consumer = new Consumer(channel, "stocks", "europe");
Of course you can mix all these channels and get them from the same ChannelFactory.
Additionally you can use the ServerApp API if you don’t want to build the url yourself:
ServerApp serverApp = new ServerApp("/myapp", false, "localhost", 8080); ChannelFactory channelFactory = new JMFChannelFactory(); channelFactory.start(); MessagingChannel lpChannel = channelFactory.newMessagingChannel("long-polling", "longPollingChannel", serverApp); MessagingChannel wsChannel = channelFactory.newMessagingChannel("websocket", "websocketChannel", serverApp); MessagingChannel udpChannel = channelFactory.newMessagingChannel("udp", "udpChannel", serverApp);
The ServerSession API has also been updated.
You can now use the ServerSession to build Consumer and Producer objects:
ServerSession serverSession = new ServerSession("/myapp", "localhost", 8080); serverSession.setMessagingTransport("websocket", new JettyWebSocketTransport()); serverSession.start(); Consumer lpConsumer = serverSession.getConsumer("long-polling", "stocks", "europe"); Consumer wsConsumer = serverSession.getConsumer("websocket", "stocks", "usa"); Consumer udpConsumer = serverSession.getConsumer("websocket", "stocks", "asia");
The GraniteDS Team.
]]>Tide.getInstance().getContext().serviceInitializer = new DefaultServiceInitializer('/myapp');
Should be replaced by:
Tide.getInstance().mainServerSession.serverApp = new SimpleServerApp('/myapp');
Why this (apparently) cosmetic change ? The difference is that you can now create other ServerSession objects for other server applications (other context roots, other server urls, …). Implicit service proxies will still be routed to the main ServerSession, if you want to connect to another server, you will have to attach the service proxies to the other ServerSession:
Tide.getInstance().mainServerSession.serverApp = new SimpleServerApp('/myapp'); var appSession2:ServerSession = new ServerSession('/myapp2', false, 'myserver2', '8080'); Tide.getInstance().getContext().appSession2 = appSession2; Tide.getInstance().getContext().myService2 = new MyService2(appSession2); // Service proxy for MyService on http://localhost:8080/myapp [Inject] public var myService:MyService; // Service proxy for MyService2 on http://myserver2:8080/myapp2 [Inject] public var myService2:MyService2;
The GraniteDS Team.
]]>The most visible change in this release is the new build process which is now entirely based on Gradle: instead of several Git repositories (one for each module), you will now find everything under a single repository called . The documentation is now based on , a much lighter solution than our previous docbook generation. This heavy lifting of our build tools was long needed and should help us to bring more frequent releases. It will also make much easier to contribute to the project, either code or docs by simple pull requests in github.
On the technical features side, here are some highlights:
Of course, many other bug fixes and improvements are also packaged in this release and you can find the complete list on Jira.
We plan to release the final 3.0.0 version at early/mid November. The APIs are now final and we won’t add any new feature, but focus only on bug fixes, documentation, examples and tutorials. You are thus invited to use the RC1 as soon as possible and report quickly anything that looks like a bug.
The next major release will introduce at least these new features or improvements:
You can refer to the previous post for the release announcement of 3.0.0.M3 for the new licensing of the different modules. To summarize, all server and basic libraries are now released under the LGPL 2.1 (previously LGPL 2.0), and all advanced client libraries and UDP client and server integration are released under a dual GPL 3.0 / commercial license.
All current users of GraniteDS can send a request at [email protected] for a free one year ‘early bird license’ for all these libraries until the final 3.0.0.GA release.
There have been once again some changes in the client APIs which will require some changes in your code. Be sure to check our posts about “Migrating from GraniteDS 3.0.0.M3 to 3.0.0.RC1 (Flex)” and “Migrating from GraniteDS 3.0.0.M3 to 3.0.0.RC1 (JavaFX)”.
Comments are welcome,
The GraniteDS Team.
]]>A sample application on Github is a ready to use demonstration of this new feature: follow the instructions on the (README.md).
]]>Basically because we love AMF but AMF isn’t good for Java client applications: with AMF, for example, all Java numeric types (byte, short, int, long, float, double, etc.) are serialized either as ActionScript int or Number, which leads to useless conversions in the context of a Java to Java data exchange and can even lead to a loss of precision (large long values cannot be represented as Numbers without truncation). Furthermore, all collections (java.util.*) are normalized to a single ArrayCollection type, BigDecimals are converted to Numbers, enum values to nothing standard (ActionScript has no enum types), etc.
Using AMF with Java clients is certainly possible, and we do have AMF support for Java clients in GraniteDS, but it is pointlessly complicated and, as a result, requires a full bunch of ugly workarounds.
That said, JMF is largely inspired by AMF and designed with the following goals in mind:
You basically need to do nothing to use JMF: HTTP headers of serialized data, unless you have configured your Java client application to do otherwise, use a specific mime-type which tells the GraniteDS backend to switch to JMF instead of AMF serialization for the incoming request and corresponding response.
The JMF mime-type is currently “application/x-jmf+amf”, the “+amf” part stating that the messages inside the request are instances of flex.messaging.messages.Message (possibly enclosed in an AMF0 envelop). We are considering to use our own message envelops when using JMF and the mime-type is likely going to be “application/x-jmf” only in later versions.
The good thing with this mime-type switch mechanism is that you can have Java (or even JavaFX / Android) client applications using the exact same backend used by Flex client applications: there is nothing specific to configure on the server-side.
With small messages such as a ping message (which purpose is to make sure that the server is alive and to negotiate serialization options), JMF vs. AMF vs. Standard Java Serialization (ie. java.io.ObjectOutputStream) gives these results:
With bigger messages (a list of person with collections of contacts), you can typically get these results:
As a general result, but without conducting an extensive benchmark, we have found that:
AMF serialization doesn’t state anything about what is (de)serializable or not: this lack of control can lead to a security breach that was discovered by Wouter Coekaerts and reported . Granite Data Services fixed this issue by adding a AMF3DeserializerSecurizer that controls which class can be instantiated at deserialization time (see this post about GraniteDS 2.2.1.GA).
Unlike AMF but just like the standard Java serialization, JMF only encodes primitive types and classes that implement java.io.Serializable. As a result, the above vulnerability doesn’t affect JMF, unless classes that shouldn’t be serialized implement (which is a design bug) java.io.Serializable.
Not yet. However, you are free to have look at the implementation, it’s fully open-source, just like the rest of the GraniteDS platform.
]]>This new release is a very important milestone on our way to the final 3.0.0.GA version, both from technical and business point-of-views:
Beside the usual , the 3.0.0.M3 version of our platform brings the following new features (some of them were already present in previous milestones, though at an early development stage):
Our focus for the next release (likely a 3.0.0.RC1) will be on Android, UDP-based real-time messaging and JSON support.
Documentation and tutorials need an update, we will post new resources as soon as they are available.
GraniteDS has always been an avid supporter of open source software. Our business model so far has been to sale yearly subscriptions to an enterprise version of our platform, embedding some very advanced features absent from the community version, together with an access to professional support services.
While we have had and still have some success with this model, it is clearly targeted to mid / big organizations developing critical applications: most of our users are simply using the community version, either because they don’t need any support or can’t afford to pay for our enterprise subscription packages. As a result, generated revenues are below what we need to move to the next step and truly develop and promote GraniteDS in the long run.
After considering different options, we have decided to move to the following business model:
We sincerely hope that you will understand this move, which is, from our point-of-view, pretty fair: open source and free software is great and must stay free for people developing open source software as well. For those who develop commercial software, and unless they can live with the basic features of our platform, an affordable and predictable financial contribution is now asked.
Before asking us any question about this new business model / commercial license, please read our licensing FAQ and the license itself.
In the 3.0.0.M3 release, only the Granite Android and JavaFX Libraries are released under GPL 3.0, with the option of purchasing a commercial license: this includes Tide for Java client applications, but the basic Java client features (eg. low-level remoting and real-time messaging) are still LGPL. Everything else in the platform is LGPL (server-side libraries, Flex client libraries, code generation tools).
Starting with the next 3.0.0.RC1 release, two other products will be available under the same dual-licensing scheme (GPL 3.0 / GraniteDS SLA):
Again, we are and will be offering free commercial licenses for early birds, so it is not going to hurt your wallet if you fast enough (the final 3.0.0.GA won’t be released before early October at the earliest).
Comments are welcome, The GraniteDS Team.
]]>This release is the second milestone for GraniteDS 3.0 and mostly contains bug fixes and improvements (see the complete changelog on JIRA ).
This release introduces the new experimental JMF protocol that will replace the AMF protocol when serializing objects between Java servers and Java clients. More details on this in a later post.
It also includes many improvements and bug fixes on the JavaFX client libraries, like integration with CDI/Weld on the client and a view scope for Spring and CDI. The JavaFX tutorial will soon be updated to demonstrate these features. Thanks a lot to Daniele Renda who was extremely helpful improving/testing/using these JavaFX libraries on a real-world application.
Thanks also to Erguder Bekrek, Thomas Ott and clemieux for their patches and help on the Flex libraries.
The tutorials and Maven archetypes will be upgraded to 3.0 M2 soon.
Important note:
GraniteDS 3.0.0.M1 has introduced support for a websocket transport in both Flex and Java clients. As this is still an experimental feature not yet released in a stable release and not really used by a lot of people, we are strongly considering moving this feature to the enterprise version of GraniteDS and complete the development of this feature only in the enterprise version. Thus all websocket related source files will likely be removed from github very soon.
Upgrading from 2.x to 3.0.x :
You can find the reference documentation here :
The Eclipse tooling of GDS 3.0 (Gas3 builder and wizard) can be updated from our Eclipse .
]]>In a first naive implementation, we could simply think of an event handler that will manually validate the input on the fly :
textField.setOnKeyTyped(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent event) { String text = ((TextInputControl)event.getTarget()).getText()); if (text.length() < 2 || text.length() > 25) ((Node)event.getTarget()).setStyle("-fx-border-color: red"); else ((Node)event.getTarget()).setStyle("-fx-border-color: null"); } });
Here we have processed the validation manually, but we now would like to be able to leverage the Bean Validation API to simplify this. Bean Validation is not meant to be applied on individual values (as the value of the input) but on data beans holding the validation constraint annotations. However in general a text field would be used to populate a data bean, so we could do something like this :
public class Person { @Size(min=2, max=20) private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
Having defined this bean, we could change the previous code by this :
final Validator validator = validatorFactory.getValidator(Person.class); textField.setOnKeyTyped(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent event) { String text = ((TextInputControl)event.getTarget()).getText()); Set<ConstraintViolation<Person>> violations = validator.validateValue(Person.class, "name", text); if (!violations.isEmpty()) ((Node)event.getTarget()).setStyle("-fx-border-color: red"); else ((Node)event.getTarget()).setStyle("-fx-border-color: null"); } });
This is definetely better, but we still have to do this for each and every input field, which is not very convenient.
One possibility would be to write a reusable validation handler, like this :
public class ValidationEventHandler<T> extends EventHandler<KeyEvent> { private Class<T> beanClass; private String propertyName; public ValidationEventHandler(Class<T> beanClass, String propertyName) { this.beanClass = beanClass; this.propertyName = propertyName; } @Override public void handle(KeyEvent event) { String text = ((TextInputControl)event.getTarget()).getText()); Set<ConstraintViolation<T>> violations = validator.validateValue(beanClass, propertyName, text); if (!violations.isEmpty()) ((Node)event.getTarget()).setStyle("-fx-border-color: red"); else ((Node)event.getTarget()).setStyle("-fx-border-color: null"); } }
Which would lead to this on each field :
textField.setOnKeyTyped( new ValidationEventHandler<Person>(Person.class, "name"));
Still we have to define this handler on each input field, so it can be configured with the bean class and property.
If we want to go a bit further, we will need a generic way to determine the links between input fields and beans. This link is application-specific, but if the application uses data binding, we can maybe find this information.
textField.text().bindBidirectional(person.name());
Unfortunately, there does not seem to be any generic reflection API on property bindings. Using the following extremely ugly hack we can find the target of a particular binding:
private Property<?> lookupBindingTarget(Property<?> inputProperty) { try { Field fh = inputProperty.getClass().getDeclaredField("helper"); fh.setAccessible(true); Object helper = fh.get(inputProperty); Field fcl = helper.getClass().getDeclaredField("changeListeners"); fcl.setAccessible(true); Object changeListeners = fcl.get(helper); if (changeListeners != null && Array.getLength(changeListeners) > 0) { ChangeListener<?> cl = (ChangeListener<?>)Array.get(changeListeners, 0); try { Field fpr = cl.getClass().getDeclaredField("propertyRef2"); fpr.setAccessible(true); WeakReference<?> ref= (WeakReference<?>)fpr.get(cl); Property<?> p = (Property<?>)ref.get(); return p; } catch (NoSuchFieldException e) { log.debug("Field propertyRef2 not found on " + cl, e); return null; } } log.debug("Could not find target for property %s", inputProperty); return null; } catch (Exception e) { log.warn(e, "Could not find target for property %s", inputProperty); return null; } }
Using this hack, We are now able to determine automatically the beans to validate for all input fields in a form. This is exactly what the FormValidator class provided by GraniteDS is built to do. It scans all inputs in a JavaFX container and add listeners on them. It then calls the corresponding validator on the target bean and propagates the constraint violations to the input by dispatching a particular event of type ValidationResultEvent.
Using FormValidator with data binding, you would simply have to do this:
FormValidator formValidator = new FormValidator(); formValidator.setForm(formContainer);
Then all bound input fields will be validated on-the-fly depending on the Bean Validation constraints defined on the target beans. The FormValidator dispatches validation events on the container so you can react and display hints and error messages to the user:
formContainer.addEventHandler(ValidationResultEvent.ANY, new EventHandler<ValidationResultEvent>() { @Override public void handle(ValidationResultEvent event) { if (event.getEventType() == ValidationResultEvent.INVALID) ((Node)event.getTarget()).setStyle("-fx-border-color: red"); else if (event.getEventType() == ValidationResultEvent.VALID) ((Node)event.getTarget()).setStyle("-fx-border-color: null"); } });
Note that for now, the FormValidator requires that the data bean implements the GraniteDS interface DataNotifier which basically means that the bean is able to dispatch JavaFX events. This requirement could be possibly removed in a future release.
public interface DataNotifier extends EventTarget { public <T extends Event> void addEventHandler(EventType<T> type, EventHandler<? super T> handler); public <T extends Event> void removeEventHandler(EventType<T> type, EventHandler<? super T> handler); }
The bean would then be implemented like this :
public class Person { private EventHandlerManager __handlerManager = new EventHandlerManager(this); @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain t) { return t.prepend(__handlerManager); } public <T extends Event> void addEventHandler(EventType<T> type, EventHandler<? super T> handler) { __handlerManager.addEventHandler(type, handler); } public <T extends Event> void removeEventHandler(EventType<T> type, EventHandler<? super T> handler) { __handlerManager.removeEventHandler(type, handler); } }
This is once again not ideal but this can be easily extracted in an abstract class.
The validation step of the FormValidator is as follows:
Set<ConstraintViolation<Object>> allViolations = validatorFactory.getValidator().validate((Object)entity, groups); Map<Object, Set<ConstraintViolation<?>>> violationsMap = new HashMap<Object, Set<ConstraintViolation<?>>>(); for (ConstraintViolation<Object> violation : allViolations) { Object rootBean = violation.getRootBean(); Object leafBean = violation.getLeafBean(); Object bean = leafBean != null && leafBean instanceof DataNotifier ? leafBean : rootBean; Set<ConstraintViolation<?>> violations = violationsMap.get(bean); if (violations == null) { violations = new HashSet<ConstraintViolation<?>>(); violationsMap.put(bean, violations); } violations.add(violation); } for (Object bean : violationsMap.keySet()) { if (bean instanceof DataNotifier) { ConstraintViolationEvent event = new ConstraintViolationEvent( ConstraintViolationEvent.CONSTRAINT_VIOLATION, violationsMap.get(bean) ); Event.fireEvent((DataNotifier)bean, event); } }
Obviously that would be easier if we could plug in the bean validation lifecycle of each bean and fire the events during the validation itself, itself of doing this kind of postprocessing.
Conclusion
We have already most of the building blocks to integrate JavaFX and Bean Validation but it is definitely not as smooth as it should be.
The main pain points are the following :
In fact, the two last points could be totally unneccessary if the validation was directly built-in the data binding feature, much like converters. It is probably possible to build something like a ValidatableStringProperty, we will let this for a future post.
]]>We didn’t have time to include those SWCs in our binary release, but we will include them in the next upcoming release (or milestone). Keep in mind that GraniteDS 3.0.0.M1 is still in a beta stage and that you can experience unexpected gitchs (report them in our ).
If you want to build yourself GraniteDS SWCs against the Apache Flex release, proceed as follows:
1. Download Apache Flex(R) 4.8.0 and unzip/untar it somewhere (say “flex48″). 2. Spawn a command shell and type (Apache Ant must be installed):
cd flex48/framework ant thirdparty-downloads
Answer yes to all questions.
3. Download GraniteDS 3.0.0.M1 sources and unzip it somewhere (say “gds30″). 4. Edit env.properties and env45.properties, and fix the FLEX_HOME variable (absolute path to flex48). 5. Then run:
cd gds30 ant -f build.xml ant -f build-flex45.xml
The first build will fail after building granite-essentials.swc, when trying to compile granite.swc: just ignore that error and proceed with build-flex45.xml.
The two GraniteDS SWCs buit against Apache Flex 4.8.0 should be in the build directory (granite-essentials.swc and granite-flex45.swc): just rename them to granite-essentials-flex48.swc and granite-flex48.swc, you’re done.
]]>