Java tutorial
/* * * Copyright (c) 2016 Caricah <info@caricah.com>. * * Caricah licenses this file to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. * * * * */ package com.caricah.iotracah.core.worker; import com.caricah.iotracah.bootstrap.data.messages.*; import com.caricah.iotracah.bootstrap.data.messages.base.IOTMessage; import com.caricah.iotracah.bootstrap.data.models.subscriptions.IotSubscription; import com.caricah.iotracah.bootstrap.exceptions.UnRetriableException; import com.caricah.iotracah.bootstrap.security.realm.state.IOTClient; import com.caricah.iotracah.core.modules.Worker; import com.caricah.iotracah.core.worker.exceptions.ShutdownException; import com.caricah.iotracah.core.worker.state.Constant; import com.caricah.iotracah.core.worker.state.SessionResetManager; import com.mashape.unirest.http.Unirest; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import io.netty.handler.codec.mqtt.MqttQoS; import org.apache.commons.configuration.Configuration; import org.apache.ignite.IgniteCache; import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.CacheMemoryMode; import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cache.eviction.lru.LruEvictionPolicy; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.shiro.session.Session; import rx.Observable; import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author <a href="mailto:bwire@caricah.com"> Peter Bwire </a> * @version 1.0 8/15/15 */ public class DumbWorker extends Worker { private ConcurrentHashMap<String, Set<String>> subscriptions = new ConcurrentHashMap<>(); // private IgniteCache<String, Set<String>> subscriptions = null; /** * <code>configure</code> allows the base system to configure itself by getting * all the settings it requires and storing them internally. The plugin is only expected to * pick the settings it has registered on the configuration file for its particular use. * * @param configuration * @throws UnRetriableException */ @Override public void configure(Configuration configuration) throws UnRetriableException { boolean configAnnoymousLoginEnabled = configuration.getBoolean(CORE_CONFIG_WORKER_ANNONYMOUS_LOGIN_ENABLED, CORE_CONFIG_WORKER_ANNONYMOUS_LOGIN_ENABLED_DEFAULT_VALUE); log.debug(" configure : Anonnymous login is configured to be enabled [{}]", configAnnoymousLoginEnabled); setAnnonymousLoginEnabled(configAnnoymousLoginEnabled); String configAnnoymousLoginUsername = configuration.getString(CORE_CONFIG_WORKER_ANNONYMOUS_LOGIN_USERNAME, CORE_CONFIG_ENGINE_WORKER_ANNONYMOUS_LOGIN_USERNAME_DEFAULT_VALUE); log.debug(" configure : Anonnymous login username is configured to be [{}]", configAnnoymousLoginUsername); setAnnonymousLoginUsername(configAnnoymousLoginUsername); String configAnnoymousLoginPassword = configuration.getString(CORE_CONFIG_WORKER_ANNONYMOUS_LOGIN_PASSWORD, CORE_CONFIG_ENGINE_WORKER_ANNONYMOUS_LOGIN_PASSWORD_DEFAULT_VALUE); log.debug(" configure : Anonnymous login password is configured to be [{}]", configAnnoymousLoginPassword); setAnnonymousLoginPassword(configAnnoymousLoginPassword); int keepaliveInSeconds = configuration.getInt(CORE_CONFIG_WORKER_CLIENT_KEEP_ALIVE_IN_SECONDS, CORE_CONFIG_WORKER_CLIENT_KEEP_ALIVE_IN_SECONDS_DEFAULT_VALUE); log.debug(" configure : Keep alive maximum is configured to be [{}]", keepaliveInSeconds); setKeepAliveInSeconds(keepaliveInSeconds); } /** * <code>initiate</code> starts the operations of this system handler. * All excecution code for the plugins is expected to begin at this point. * * @throws UnRetriableException */ @Override public void initiate() throws UnRetriableException { //Initiate the session reset manager. SessionResetManager sessionResetManager = new SessionResetManager(); sessionResetManager.setWorker(this); sessionResetManager.setDatastore(this.getDatastore()); setSessionResetManager(sessionResetManager); String igniteCacheName = "dumbTester"; CacheConfiguration clCfg = new CacheConfiguration(); clCfg.setName(igniteCacheName); clCfg.setAtomicityMode(CacheAtomicityMode.ATOMIC); clCfg.setCacheMode(CacheMode.PARTITIONED); clCfg.setMemoryMode(CacheMemoryMode.ONHEAP_TIERED); LruEvictionPolicy lruEvictionPolicy = new LruEvictionPolicy(5170000); clCfg.setEvictionPolicy(lruEvictionPolicy); clCfg.setSwapEnabled(true); //if(subscriptions instanceof IgniteCache) { //subscriptions = getIgnite().createCache(clCfg); //} //Initiate unirest properties. Unirest.setTimeouts(5000, 5000); } /** * <code>terminate</code> halts excecution of this plugin. * This provides a clean way to exit /stop operations of this particular plugin. */ @Override public void terminate() { //Shutdown unirest. try { Unirest.shutdown(); } catch (IOException e) { log.warn(" terminate : problem closing unirest", e); } } /** * Provides the Observer with a new item to observe. * <p> * The {@link com.caricah.iotracah.core.modules.Server} may call this method 0 or more times. * <p> * The {@code Observable} will not call this method again after it calls either {@link #onCompleted} or * {@link #onError}. * * @param iotMessage the item emitted by the Observable */ @Override public void onNext(IOTMessage iotMessage) { getExecutorService().submit(() -> { log.info(" onNext : received {}", iotMessage); try { IOTMessage response = null; switch (iotMessage.getMessageType()) { case ConnectMessage.MESSAGE_TYPE: ConnectMessage connectMessage = (ConnectMessage) iotMessage; response = ConnectAcknowledgeMessage.from(connectMessage.isDup(), connectMessage.getQos(), connectMessage.isRetain(), connectMessage.getKeepAliveTime(), MqttConnectReturnCode.CONNECTION_ACCEPTED); break; case SubscribeMessage.MESSAGE_TYPE: SubscribeMessage subscribeMessage = (SubscribeMessage) iotMessage; List<Integer> grantedQos = new ArrayList<>(); subscribeMessage.getTopicFilterList().forEach(topic -> { String topicKey = quickCheckIdKey("", Arrays.asList(topic.getKey().split(Constant.PATH_SEPARATOR))); Set<String> channelIds = subscriptions.get(topicKey); if (Objects.isNull(channelIds)) { channelIds = new HashSet<>(); } channelIds.add(subscribeMessage.getConnectionId()); subscriptions.put(topicKey, channelIds); grantedQos.add(topic.getValue()); }); response = SubscribeAcknowledgeMessage.from(subscribeMessage.getMessageId(), grantedQos); break; case UnSubscribeMessage.MESSAGE_TYPE: UnSubscribeMessage unSubscribeMessage = (UnSubscribeMessage) iotMessage; response = UnSubscribeAcknowledgeMessage.from(unSubscribeMessage.getMessageId()); break; case Ping.MESSAGE_TYPE: response = iotMessage; break; case PublishMessage.MESSAGE_TYPE: PublishMessage publishMessage = (PublishMessage) iotMessage; Set<String> matchingTopics = getMatchingSubscriptions("", publishMessage.getTopic()); for (String match : matchingTopics) { Set<String> channelIds = subscriptions.get(match); if (Objects.nonNull(channelIds)) { channelIds.forEach(id -> { PublishMessage clonePublishMessage = publishMessage.cloneMessage(); clonePublishMessage.copyTransmissionData(iotMessage); clonePublishMessage.setConnectionId(id); pushToServer(clonePublishMessage); }); } } if (MqttQoS.AT_MOST_ONCE.value() == publishMessage.getQos()) { break; } else if (MqttQoS.AT_LEAST_ONCE.value() == publishMessage.getQos()) { response = AcknowledgeMessage.from(publishMessage.getMessageId()); break; } case PublishReceivedMessage.MESSAGE_TYPE: case ReleaseMessage.MESSAGE_TYPE: case CompleteMessage.MESSAGE_TYPE: case DisconnectMessage.MESSAGE_TYPE: case AcknowledgeMessage.MESSAGE_TYPE: default: DisconnectMessage disconnectMessage = DisconnectMessage.from(true); disconnectMessage.copyTransmissionData(iotMessage); throw new ShutdownException(disconnectMessage); } if (Objects.nonNull(response)) { response.copyTransmissionData(iotMessage); pushToServer(response); } } catch (ShutdownException e) { IOTMessage response = e.getResponse(); if (Objects.nonNull(response)) { pushToServer(response); } } catch (Exception e) { log.error(" onNext : Serious error that requires attention ", e); } }); } private Set<String> getMatchingSubscriptions(String partition, String topic) { Set<String> topicFilterKeys = new HashSet<>(); ListIterator<String> pathIterator = Arrays.asList(topic.split(Constant.PATH_SEPARATOR)).listIterator(); List<String> growingTitles = new ArrayList<>(); while (pathIterator.hasNext()) { String name = pathIterator.next(); List<String> slWildCardList = new ArrayList<>(growingTitles); if (pathIterator.hasNext()) { //We deal with wildcard. slWildCardList.add(Constant.SINGLE_LEVEL_WILDCARD); topicFilterKeys.add(quickCheckIdKey(partition, slWildCardList)); } else { //we deal with full topic slWildCardList.add(name); } List<String> reverseSlWildCardList = new ArrayList<>(slWildCardList); growingTitles.add(name); int sizeOfTopic = slWildCardList.size(); if (sizeOfTopic > 1) { sizeOfTopic -= 1; for (int i = 0; i < sizeOfTopic; i++) { if (i >= 0) { slWildCardList.set(i, Constant.MULTI_LEVEL_WILDCARD); reverseSlWildCardList.set(sizeOfTopic - i, Constant.MULTI_LEVEL_WILDCARD); topicFilterKeys.add(quickCheckIdKey(partition, slWildCardList)); topicFilterKeys.add(quickCheckIdKey(partition, reverseSlWildCardList)); } } } } topicFilterKeys.add(quickCheckIdKey(partition, growingTitles)); return topicFilterKeys; } private static String quickCheckIdKey(String partition, List<String> nameParts) { return getPartitionAsInitialParentId(partition) + ":" + String.join(":", nameParts); } private static String getPartitionAsInitialParentId(String partition) { return "p[" + partition + "]"; } @Override public void onStart(Session session) { } @Override public void onStop(Session session) { postSessionCleanUp((IOTClient) session, false); } @Override public void onExpiration(Session session) { log.debug(" onExpiration : -----------------------------------------------------"); log.debug(" onExpiration : ------- We have an expired session {} -------", session); log.debug(" onExpiration : -----------------------------------------------------"); postSessionCleanUp((IOTClient) session, true); } private void postSessionCleanUp(IOTClient iotClient, boolean isExpiry) { if (isExpiry) { log.debug(" postSessionCleanUp : ---------------- We are to publish a will man for {}", iotClient); publishWill(iotClient); } //Notify the server to remove this client from further sending in requests. DisconnectMessage disconnectMessage = DisconnectMessage.from(false); disconnectMessage = iotClient.copyTransmissionData(disconnectMessage); pushToServer(disconnectMessage); // Unsubscribe all if (iotClient.getIsCleanSession()) { Observable<IotSubscription> subscriptionObservable = getDatastore().getSubscriptions(iotClient); subscriptionObservable.subscribe(subscription -> getMessenger().unSubscribe(subscription) , throwable -> log.error(" postSessionCleanUp : problems while unsubscribing", throwable) , () -> { Observable<PublishMessage> publishMessageObservable = getDatastore().getMessages(iotClient); publishMessageObservable.subscribe(getDatastore()::removeMessage, throwable -> { log.error(" postSessionCleanUp : problems while unsubscribing", throwable); // any way still delete it from our db }, () -> { // and delete it from our db }); }); } } }