Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functional adapters for Application and ApplicationExtended interfaces #212

Merged
merged 7 commits into from
Nov 24, 2018
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,40 @@ Message crackers for each FIX version are still generated for backward compatibi

The generated classes define handlers for all messages defined by that version of FIX. This requires the JVM to load those classes when the cracker is loaded. Most applications only need to handle a small subset of the messages defined by a FIX version so loading all the messages classes is excessive overhead in those cases.

#### Functional interfaces for receiving messages

If you prefer using lambda expressions in handling received messages, then <code>ApplicationFunctionalAdapter</code> or <code>ApplicationExtendedFunctionalAdapter</code> can be used to register reactions to events the application is interested in.

They also allow registering the interests in a given message type in a type-safe manner.

```
import quickfix.ApplicationFunctionalAdapter;
import quickfix.SessionID;

public class EmailForwarder {
public void init(ApplicationFunctionalAdapter adapter) {
adapter.addOnLogonListener(this::captureUsername);
adapter.addFromAppListener(quickfix.fix44.Email.class, (e , s) -> forward(e));
}

private void forward(quickfix.fix44.Email email) {
// implementation
}

private void captureUsername(SessionID sessionID) {
// implementation
}
}
```
<code>ApplicationFunctionalAdapter</code> and <code>ApplicationExtendedFunctionalAdapter</code> support multiple registration to the same event, and the registered callbacks are invoked in the FIFO manner.

However FIFO cannot be guaranteed between registration with specific message type (e.g. <code>quickfix.fix44.Email</code>) and that without specific message type. For example, there is no invocation order guarantee between the following two callbacks:

```
adapter.addFromAppListener((e , s) -> handleGeneral(e));

adapter.addFromAppListener(quickfix.fix44.Email.class, (e , s) -> handleSpecific(e));
```

### Sending messages

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,48 @@ <H3>Most Type Safe... DO THIS!</H3>
This requires the JVM to load those classes when the cracker is loaded. Most applications only need to handle a small subset of the messages defined by
a FIX version so loading all the messages classes is excessive overhead in those cases.
<p>

<H3>Support for Functional Interfaces</H3>
<p>
If you prefer using lambda expressions in handling received messages, then
<code>ApplicationFunctionalAdapter</code> or <code>ApplicationExtendedFunctionalAdapter</code> can be used to
register reactions to events the application is interested in. They also allow registering the interests in a given
message type in a type-safe manner.
<p>

<pre class="code">
import quickfix.ApplicationFunctionalAdapter;
import quickfix.SessionID;

public class EmailForwarder {
public void init(ApplicationFunctionalAdapter adapter) {
adapter.addOnLogonListener(this::captureUsername);
adapter.addFromAppListener(quickfix.fix44.Email.class, (e , s) -> forward(e));
}

private void forward(quickfix.fix44.Email email) {
// implementation
}

private void captureUsername(SessionID sessionID) {
// implementation
}
}
</pre>
<p>
<code>ApplicationFunctionalAdapter</code> and <code>ApplicationExtendedFunctionalAdapter</code> support multiple
registration to the same event, and the registered callbacks are invoked in the FIFO manner. However FIFO
cannot be guaranteed between registration with specific message type (e.g. <code>quickfix.fix44.Email</code>) and
that without specific message type. For example, there is no invocation order guarantee between the following two
callbacks:
<p>

<pre class="code">
adapter.addFromAppListener((e , s) -> handleGeneral(e));

adapter.addFromAppListener(quickfix.fix44.Email.class, (e , s) -> handleSpecific(e));
</pre>

<div class="footer">More information at <a href="http://www.quickfixj.org/">www.quickfixj.org</a></div>

</body>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package quickfix;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
* This is an adapter implementation of ApplicationExtended interface that transforms the usage into more
* functional style. It breaks down each interface method into a number of single-method interfaces, which can be
* supplied with lambda expressions. Each single-method interface has its own add and remove listener method.
*
* <ol>
* <li>Support multiple listeners of the same operation, e.g. onLogon. The method of the listeners will be invoked
* in the same order of when add method was invoked, i.e. FIFO</li>
* <li>Support fail fast evaluation of canLogOn listeners. False will be returned for the first false value returned
* from the predicate of canLogOn. Otherwise return true.</li>
* <li>Provides a thread-safe way to delegate to, add and remove listeners under the assumption that adding and
* removing listeners are rare.</li>
* </ol>
*/
public class ApplicationExtendedFunctionalAdapter extends ApplicationFunctionalAdapter implements ApplicationExtended {
private final List<Predicate<SessionID>> canLogonPredicates = new CopyOnWriteArrayList<>();
private final List<Consumer<SessionID>> onBeforeSessionResetListeners = new CopyOnWriteArrayList<>();

/**
* Add a Predicate of Session to the canLogon evaluation.
*
* @param canlogon the Predicate of Session to the canLogon evaluation.
*/
public void addCanLogOnPredicate(Predicate<SessionID> canlogon) {
canLogonPredicates.add(canlogon);
}

/**
* Remove a Predicate of Session from the canLogon evaluation.
*
* @param canlogon the Predicate of Session to the canLogon evaluation.
*/
public void removeCanLogOnPredicate(Predicate<SessionID> canlogon) {
canLogonPredicates.remove(canlogon);
}

/**
* Add a Consumer of SessionID to listen to onBeforeSessionReset operation.
*
* @param onBeforeSessionReset the Consumer of SessionID to listen to onBeforeSessionReset operation.
*/
public void addOnBeforeSessionResetListener(Consumer<SessionID> onBeforeSessionReset) {
onBeforeSessionResetListeners.add(onBeforeSessionReset);
}

/**
* Remove a Consumer of SessionID from onBeforeSessionReset operation.
*
* @param onBeforeSessionReset the Consumer of SessionID to listen to onBeforeSessionReset operation.
*/
public void removeBeforeSessionResetListener(Consumer<SessionID> onBeforeSessionReset) {
onBeforeSessionResetListeners.remove(onBeforeSessionReset);
}

@Override
public boolean canLogon(SessionID sessionID) {
return canLogonPredicates.stream()
.allMatch(p -> p.test(sessionID));
}

@Override
public void onBeforeSessionReset(SessionID sessionID) {
onBeforeSessionResetListeners.forEach(c -> c.accept(sessionID));
}

}
Loading