|
|
Line 1: |
Line 1: |
− | {{ScoutPage|cat=HowTo 4.0}}
| + | The Scout documentation has been moved to https://eclipsescout.github.io/. |
− | | + | |
− | This how-to describes how to implement client notifications as described on the [[Scout/Concepts/Client Notification|Client Notification Concept page]].
| + | |
− | | + | |
− | = Summary<br> =
| + | |
− | | + | |
− | The following classes need to be changed or added to support this:
| + | |
− | | + | |
− | '''shared'''
| + | |
− | | + | |
− | org.eclipsescout.demo.minicrm.shared\META-INF\MANIFEST.MF
| + | |
− | org.eclipsescout.demo.minicrm.shared.CreateNotificationPermission
| + | |
− | org.eclipsescout.demo.minicrm.shared.notification.XxxxNotification (one for each event, where Xxxx is the name of the event)
| + | |
− | org.eclipsescout.demo.minicrm.shared.services.process.INotificationService
| + | |
− | | + | |
− | '''server'''
| + | |
− | | + | |
− | org.eclipsescout.demo.minicrm.server\plugin.xml
| + | |
− | org.eclipsescout.demo.minicrm.server.services.custom.security.AccessControlService
| + | |
− | org.eclipsescout.demo.minicrm.server.NotificationService
| + | |
− | | + | |
− | '''client'''
| + | |
− | | + | |
− | org.eclipsescout.demo.minicrm.client\plugin.xml
| + | |
− | org.eclipsescout.demo.minicrm.client.ClientSession
| + | |
− | org.eclipsescout.demo.minicrm.client.ui.desktop.Desktop
| + | |
− | org.eclipsescout.demo.minicrm.client.services.IMyNotificationConsumerService
| + | |
− | org.eclipsescout.demo.minicrm.client.services.MyNotificationConsumerService
| + | |
− | | + | |
− | The changes in detail are as follows:<br>
| + | |
− | | + | |
− | = shared =
| + | |
− | | + | |
− | '''org.eclipsescout.demo.minicrm.shared\META-INF\MANIFEST.MF'''
| + | |
− | | + | |
− | Add the notification package to the exported packages:
| + | |
− | <pre>Export-Package: org.eclipsescout.demo.minicrm.shared,
| + | |
− | org.eclipsescout.demo.shared.notification,
| + | |
− | ... remaining packages ...
| + | |
− | </pre>
| + | |
− | '''org.eclipsescout.demo.minicrm.shared.CreateNotificationPermission'''
| + | |
− | | + | |
− | Create this file with the content as follows:
| + | |
− | <pre>public class CreateNotificationPermission extends BasicPermission {
| + | |
− | private static final long serialVersionUID = 0L;
| + | |
− | | + | |
− | public CreateNotificationPermission() {
| + | |
− | super(CreateNotificationPermission.class.getName());
| + | |
− | }
| + | |
− | }
| + | |
− | </pre>
| + | |
− | <br> '''org.eclipsescout.demo.minicrm.shared.notification.XxxxNotification'''
| + | |
− | | + | |
− | Create a notification class for each distinct event you want to be able to signal. Optionally, add members and a constructor if you need to pass information with the notification:
| + | |
− | <pre>public class XxxxNotification extends AbstractClientNotification {
| + | |
− | private static final long serialVersionUID = 1L;
| + | |
− | /* optional private members */
| + | |
− |
| + | |
− | /* optional constructor */
| + | |
− | public XxxxNotification(/* parameters as needed */) {
| + | |
− | super();
| + | |
− | /* assigning parameters to private members as needed */
| + | |
− | }
| + | |
− | | + | |
− | @Override
| + | |
− | public boolean coalesce(IClientNotification existingNotification) {
| + | |
− | return false;
| + | |
− | }
| + | |
− | }
| + | |
− | </pre>
| + | |
− | <br> '''org.eclipsescout.demo.minicrm.shared.services.process.INotificationService'''
| + | |
− | | + | |
− | Create the skeleton for this interface:
| + | |
− | <pre>@InputValidation(IValidationStrategy.NO_CHECK.class)
| + | |
− | public interface INotificationService extends IService {
| + | |
− | }
| + | |
− | </pre>
| + | |
− | For each event that you want to notify independently, add a method (corresponding to the notifications above; add parameters if needed):
| + | |
− | | + | |
− | public void sendXxxx(/* optional parameters */) throws ProcessingException;
| + | |
− | | + | |
− | = <br> server =
| + | |
− | | + | |
− | '''org.eclipsescout.demo.minicrm.server\plugin.xml'''
| + | |
− | | + | |
− | In the extension section that contains the services, add the NotificationService:
| + | |
− | | + | |
− | <service
| + | |
− | factory="org.eclipse.scout.rt.server.services.ServerServiceFactory"
| + | |
− | class="org.eclipsescout.demo.minicrm.server.services.process.NotificationService"
| + | |
− | session="org.eclipsescout.demo.minicrm.server.ServerSession">
| + | |
− | </service>
| + | |
− | | + | |
− | '''org.eclipsescout.demo.minicrm.server.services.common.security.AccessControlService'''
| + | |
− | | + | |
− | In the method execLoadPermissions() add the permission to create notifications and wrap the whole remainder of the class (defining user based permissions) in a check for null:
| + | |
− | <source lang="java">
| + | |
− | @Override
| + | |
− | protected Permissions execLoadPermissions() {
| + | |
− | Permissions permissions = new Permissions();
| + | |
− | permissions.add(new RemoteServiceAccessPermission("*.shared.*", "*"));
| + | |
− | permissions.add(new CreateNotificationPermission());
| + | |
− | | + | |
− | if (ServerSession.get().getPersonNr() != null) {
| + | |
− | // backdoor: the first user may do everything?
| + | |
− | if (ServerSession.get().getPersonNr().equals(1L)) {
| + | |
− | logger.warn("backdoor used: person nr 1 was granted all permissions");
| + | |
− | permissions.add(new AllPermission());
| + | |
− | }
| + | |
− | else {
| + | |
− | try {
| + | |
− | | + | |
− | // get simple class names from the databse
| + | |
− | StringArrayHolder permission = new StringArrayHolder();
| + | |
− | SQL.selectInto("" +
| + | |
− | "SELECT DISTINCT P.PERMISSION_NAME " +
| + | |
− | "FROM ROLE_PERMISSION P, USER_ROLE R " +
| + | |
− | "WHERE R.USER_NR = :personNr " +
| + | |
− | "AND R.ROLE_NR = P.ROLE_NR " +
| + | |
− | "INTO :permission ",
| + | |
− | new NVPair("permission", permission));
| + | |
− | | + | |
− | // create a map from simple class names to qualified class names
| + | |
− | HashMap<String, String> map = new HashMap<String, String>();
| + | |
− | for (BundleClassDescriptor descriptor : SERVICES.getService(IPermissionService.class).getAllPermissionClasses()) {
| + | |
− | map.put(descriptor.getSimpleClassName(), descriptor.getClassName());
| + | |
− | }
| + | |
− | | + | |
− | // instantiate the real permissions and assign them
| + | |
− | for (String simpleClass : permission.getValue()) {
| + | |
− | try {
| + | |
− | permissions.add((Permission) Class.forName(map.get(simpleClass)).newInstance());
| + | |
− | }
| + | |
− | catch (Exception e) {
| + | |
− | logger.error("cannot find permission " + simpleClass + ": " + e.getMessage());
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | catch (ProcessingException e) {
| + | |
− | logger.error("cannot read permissions: " + e.getStackTrace());
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | }
| + | |
− | return permissions;
| + | |
− | }
| + | |
− | </source>
| + | |
− | '''org.eclipsescout.demo.minicrm.server.services.process.NotificationService'''
| + | |
− | | + | |
− | Create the skeleton of this class:
| + | |
− | <pre>public class NotificationService extends AbstractService implements INotificationService {
| + | |
− | private static IScoutLogger logger = ScoutLogManager.getLogger(NotificationService.class);
| + | |
− | private final static long TIMEOUT = 1000 * 60 * 10; // 10min
| + | |
− | }
| + | |
− | </pre>
| + | |
− | For each method that was added to the interface, add the implementation:
| + | |
− | | + | |
− | @Override
| + | |
− | public void sendXxxx(/* parameters as needed */) throws ProcessingException {
| + | |
− | if (!ACCESS.check(new CreateNotificationPermission())) {
| + | |
− | throw new VetoException(TEXTS.get("AuthorizationFailed"));
| + | |
− | }
| + | |
− | logger.info("queue 'Xxxx' notification on server");
| + | |
− | IClientNotificationService service = SERVICES.getService(IClientNotificationService.class);
| + | |
− | service.putNotification(new XxxxNotification(/* your parameters */), new AllUserFilter(TIMEOUT));
| + | |
− | }
| + | |
− | | + | |
− | = <br> client =
| + | |
− | | + | |
− | '''org.eclipsescout.demo.minicrm.client\plugin.xml'''
| + | |
− | | + | |
− | In the extension section that contains the services and proxies, add the service for the MyNotificationConsumerService and the proxy for the MyNotificationService interface:
| + | |
− | | + | |
− | <service
| + | |
− | factory="org.eclipse.scout.rt.client.services.ClientServiceFactory"
| + | |
− | class="org.eclipsescout.demo.minicrm.client.services.MyNotificationConsumerService"
| + | |
− | session="org.eclipsescout.demo.minicrm.client.ClientSession">
| + | |
− | </service>
| + | |
− | <proxy
| + | |
− | factory="org.eclipse.scout.rt.client.services.ClientProxyServiceFactory"
| + | |
− | class="org.eclipsescout.demo.minicrm.shared.services.process.INotificationService">
| + | |
− | </proxy>
| + | |
− | | + | |
− | '''org.eclipsescout.demo.minicrm.client.ui.desktop.Desktop'''
| + | |
− | | + | |
− | Add the following method, allowing access to the Desktop object:
| + | |
− | <source lang="java">
| + | |
− | public static Desktop get() {
| + | |
− | return (Desktop) ClientSyncJob.getCurrentSession().getDesktop();
| + | |
− | }
| + | |
− | </source>
| + | |
− | Add other getters to objects you might need in your MyNotificationConsumerService.
| + | |
− | | + | |
− | '''org.eclipsescout.demo.minicrm.client.ClientSession'''
| + | |
− | | + | |
− | Modify the execLoadSession() method to enable client notifications:
| + | |
− | <source lang="java">
| + | |
− | @Override
| + | |
− | public void execLoadSession() throws ProcessingException {
| + | |
− | setServiceTunnel(new HttpServiceTunnel(this, getBundle().getBundleContext().getProperty("server.url")));
| + | |
− |
| + | |
− | //pre-load all known code types
| + | |
− | CODES.getAllCodeTypes(org.eclipsescout.demo.minicrm.shared.Activator.PLUGIN_ID);
| + | |
− |
| + | |
− | // turn client notification polling on
| + | |
− | getServiceTunnel().setClientNotificationPollInterval(2000L);
| + | |
− |
| + | |
− | IMyNotificationConsumerService notificationHandlerService = SERVICES.getService(IMyNotificationConsumerService.class);
| + | |
− | SERVICES.getService(IClientNotificationConsumerService.class).addClientNotificationConsumerListener(this, notificationHandlerService);
| + | |
− |
| + | |
− | setDesktop(new Desktop());
| + | |
− | }
| + | |
− | </source>
| + | |
− | | + | |
− | Modify the execStoreSession() method to end client notification:
| + | |
− | <source lang="java">
| + | |
− | @Override
| + | |
− | public void execStoreSession() throws ProcessingException {
| + | |
− | ClientSession.get().getServiceTunnel().setClientNotificationPollInterval(-1);
| + | |
− | }
| + | |
− | </source>
| + | |
− | '''org.eclipsescout.demo.minicrm.client.services.IMyNotificationConsumerService'''
| + | |
− | | + | |
− | Create this empty interface:
| + | |
− | <source lang="java">
| + | |
− | public interface IMyNotificationConsumerService extends IService, IClientNotificationConsumerListener {
| + | |
− | }
| + | |
− | </source>
| + | |
− | '''org.eclipsescout.demo.minicrm.client.services.MyNotificationConsumerService'''
| + | |
− | | + | |
− | Create the MyNotification consumer class. Add a block for each event you have defined in the shared project:
| + | |
− | <source lang="java">
| + | |
− | public class MyNotificationConsumerService extends AbstractService implements IMyConsumerService {
| + | |
− | private static IScoutLogger logger = ScoutLogManager.getLogger(MyNotificationConsumerService.class);
| + | |
− | | + | |
− | @Override
| + | |
− | public void handleEvent(ClientNotificationConsumerEvent e, boolean sync) {
| + | |
− | logger.info("received client notification event for user '" + ClientSyncJob.getCurrentSession().getUserId() + "'");
| + | |
− | | + | |
− | final IClientNotification notification = e.getClientNotification();
| + | |
− | final IClientSession session = ClientSyncJob.getCurrentSession();
| + | |
− | | + | |
− | // deal with notification in async jobs to prevent blocking of the model thread
| + | |
− | if (notification instanceof XxxxNotification) {
| + | |
− | new ClientAsyncJob("async wrapper (Xxxx)", session) {
| + | |
− | @Override
| + | |
− | protected void runVoid(IProgressMonitor monitor) throws Throwable {
| + | |
− | new ClientSyncJob("Xxxx", session) {
| + | |
− | @Override
| + | |
− | protected void runVoid(IProgressMonitor monitor1) throws Throwable {
| + | |
− | Desktop desktop = Desktop.get();
| + | |
− | if (desktop != null) {
| + | |
− | /* do whatever is needed to react to this event */
| + | |
− | }
| + | |
− | }
| + | |
− | }.schedule();
| + | |
− | }
| + | |
− | }.schedule();
| + | |
− | }
| + | |
− | else if (notification instanceof XxxxNotification) {
| + | |
− | new ClientAsyncJob("async wrapper (Xxxx)", session) {
| + | |
− | @Override
| + | |
− | protected void runVoid(IProgressMonitor monitor) throws Throwable {
| + | |
− | new ClientSyncJob("Xxxx", session) {
| + | |
− | @Override
| + | |
− | protected void runVoid(IProgressMonitor monitor1) throws Throwable {
| + | |
− | Desktop desktop = Desktop.get();
| + | |
− | if (desktop != null) {
| + | |
− | /* do whatever is needed to react to this event */
| + | |
− | }
| + | |
− | }
| + | |
− | }.schedule();
| + | |
− | }
| + | |
− | }.schedule();
| + | |
− | }
| + | |
− | }
| + | |
− | }
| + | |
− | </source>
| + | |
− | <br>
| + | |
− | | + | |
− | = Triggering an event =
| + | |
− | | + | |
− | In order to trigger a client notification (either from another client or from the server), use the following line:
| + | |
− | <pre> SERVICES.getService(INotificationService.class).sendXxxx(/* optional parameters */);
| + | |
− | </pre>
| + | |