/* * Copyright 2014-2016 CyberVision, Inc. * * Licensed 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 org.kaaproject.kaa.client.event.registration; import org.kaaproject.kaa.client.channel.ProfileTransport; import org.kaaproject.kaa.client.channel.UserTransport; import org.kaaproject.kaa.client.context.ExecutorContext; import org.kaaproject.kaa.client.event.EndpointAccessToken; import org.kaaproject.kaa.client.event.EndpointKeyHash; import org.kaaproject.kaa.client.persistence.KaaClientState; import org.kaaproject.kaa.common.endpoint.gen.EndpointAttachResponse; import org.kaaproject.kaa.common.endpoint.gen.EndpointDetachResponse; import org.kaaproject.kaa.common.endpoint.gen.SyncResponseResultType; import org.kaaproject.kaa.common.endpoint.gen.UserAttachNotification; import org.kaaproject.kaa.common.endpoint.gen.UserAttachRequest; import org.kaaproject.kaa.common.endpoint.gen.UserAttachResponse; import org.kaaproject.kaa.common.endpoint.gen.UserDetachNotification; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; /** * Default {@link EndpointRegistrationManager} implementation. * * @author Taras Lemkin */ public class DefaultEndpointRegistrationManager implements EndpointRegistrationManager, EndpointRegistrationProcessor { private static final Logger LOG = LoggerFactory.getLogger( DefaultEndpointRegistrationManager.class); private static final Random RANDOM = new Random(); private final KaaClientState state; private final Map<Integer, OnAttachEndpointOperationCallback> endpointAttachListeners = new ConcurrentHashMap<>(); private final Map<Integer, OnDetachEndpointOperationCallback> endpointDetachListeners = new ConcurrentHashMap<>(); private final Map<Integer, EndpointAccessToken> attachEndpointRequests = new ConcurrentHashMap<>(); private final Map<Integer, EndpointKeyHash> detachEndpointRequests = new ConcurrentHashMap<>(); private final ExecutorContext executorContext; private UserAttachRequest userAttachRequest; private UserAttachCallback userAttachCallback; private AttachEndpointToUserCallback attachEndpointToUserCallback; private DetachEndpointFromUserCallback detachEndpointFromUserCallback; private volatile UserTransport userTransport; private volatile ProfileTransport profileTransport; /** * All-args constructor. */ public DefaultEndpointRegistrationManager(KaaClientState state, ExecutorContext executorContext, UserTransport userTransport, ProfileTransport profileTransport) { this.userTransport = userTransport; this.profileTransport = profileTransport; this.state = state; this.executorContext = executorContext; String endpointAccessToken = state.getEndpointAccessToken(); if (endpointAccessToken == null || endpointAccessToken.length() == 0) { state.refreshEndpointAccessToken(); } } private String regenerateEndpointAccessToken() { String newEndpointAccessToken = state.refreshEndpointAccessToken(); LOG.info("New endpoint access token is generated: '{}'", newEndpointAccessToken); onEndpointAccessTokenChanged(); return newEndpointAccessToken; } /** * Updates an endpoint access token. * * @param token new endpoint access token */ public void updateEndpointAccessToken(String token) { state.setEndpointAccessToken(token); state.setIfNeedProfileResync(true); onEndpointAccessTokenChanged(); } public String refreshEndpointAccessToken() { return regenerateEndpointAccessToken(); } @Override public void attachEndpoint(EndpointAccessToken endpointAccessToken, OnAttachEndpointOperationCallback resultListener) { int requestId = getRandomInt(); LOG.info("Going to attach Endpoint by access token: {}", endpointAccessToken); attachEndpointRequests.put(requestId, endpointAccessToken); if (resultListener != null) { endpointAttachListeners.put(requestId, resultListener); } if (userTransport != null) { userTransport.sync(); } } @Override public void detachEndpoint( EndpointKeyHash endpointKeyHash, OnDetachEndpointOperationCallback resultListener) { int requestId = getRandomInt(); LOG.info("Going to detach Endpoint by endpoint key hash: {}", endpointKeyHash); detachEndpointRequests.put(requestId, endpointKeyHash); if (resultListener != null) { endpointDetachListeners.put(requestId, resultListener); } if (userTransport != null) { userTransport.sync(); } } @Override public void attachUser( String userExternalId, String userAccessToken, UserAttachCallback callback) { if (UserVerifierConstants.DEFAULT_USER_VERIFIER_TOKEN != null) { attachUser(UserVerifierConstants.DEFAULT_USER_VERIFIER_TOKEN, userExternalId, userAccessToken, callback); } else { throw new IllegalStateException( "Default user verifier was not defined during SDK generation process!"); } } @Override public void attachUser(String userVerifierToken, String userExternalId, String userAccessToken, UserAttachCallback callback) { userAttachRequest = new UserAttachRequest(userVerifierToken, userExternalId, userAccessToken); userAttachCallback = callback; if (userTransport != null) { userTransport.sync(); } } public Map<EndpointAccessToken, EndpointKeyHash> getAttachedEndpointList() { return state.getAttachedEndpointsList(); } @Override public void onUpdate(List<EndpointAttachResponse> attachResponses, List<EndpointDetachResponse> detachResponses, final UserAttachResponse userResponse, final UserAttachNotification userAttachNotification, final UserDetachNotification userDetachNotification) throws IOException { if (userResponse != null) { if (userAttachCallback != null) { final UserAttachCallback callback = userAttachCallback; executorContext.getCallbackExecutor().submit(new Runnable() { @Override public void run() { callback.onAttachResult(userResponse); } }); userAttachCallback = null; } if (userResponse.getResult() == SyncResponseResultType.SUCCESS) { state.setAttachedToUser(true); if (attachEndpointToUserCallback != null) { final AttachEndpointToUserCallback callback = attachEndpointToUserCallback; executorContext.getCallbackExecutor().submit(new Runnable() { @Override public void run() { callback.onAttachedToUser(userAttachRequest.getUserExternalId(), state.getEndpointAccessToken()); } }); } } userAttachRequest = null; } if (attachResponses != null && !attachResponses.isEmpty()) { for (EndpointAttachResponse attached : attachResponses) { notifyAttachedListener(attached.getResult(), endpointAttachListeners.remove(attached.getRequestId()), new EndpointKeyHash( attached.getEndpointKeyHash())); attachEndpointRequests.remove(attached.getRequestId()); } } if (detachResponses != null && !detachResponses.isEmpty()) { for (EndpointDetachResponse detached : detachResponses) { notifyDetachedListener(detached.getResult(), endpointDetachListeners.remove(detached.getRequestId())); EndpointKeyHash endpointKeyHash = detachEndpointRequests.remove(detached.getRequestId()); if (endpointKeyHash != null && detached.getResult() == SyncResponseResultType.SUCCESS) { if (endpointKeyHash.equals(state.getEndpointKeyHash())) { state.setAttachedToUser(false); } } } } if (userAttachNotification != null) { state.setAttachedToUser(true); if (attachEndpointToUserCallback != null) { final AttachEndpointToUserCallback callback = attachEndpointToUserCallback; executorContext.getCallbackExecutor().submit(new Runnable() { @Override public void run() { callback.onAttachedToUser(userAttachNotification.getUserExternalId(), userAttachNotification.getEndpointAccessToken()); } }); } } if (userDetachNotification != null) { state.setAttachedToUser(false); if (detachEndpointFromUserCallback != null) { final DetachEndpointFromUserCallback callback = detachEndpointFromUserCallback; executorContext.getCallbackExecutor().submit(new Runnable() { @Override public void run() { callback.onDetachedFromUser(userDetachNotification.getEndpointAccessToken()); } }); } } } private void notifyAttachedListener(final SyncResponseResultType result, final OnAttachEndpointOperationCallback operationCallback, final EndpointKeyHash keyHash) { if (operationCallback != null) { executorContext.getCallbackExecutor().submit(new Runnable() { @Override public void run() { operationCallback.onAttach(result, keyHash); } }); } } private void notifyDetachedListener(final SyncResponseResultType result, final OnDetachEndpointOperationCallback operationCallback) { if (operationCallback != null) { executorContext.getCallbackExecutor().submit(new Runnable() { @Override public void run() { operationCallback.onDetach(result); } }); } } private void onEndpointAccessTokenChanged() { if (profileTransport != null) { profileTransport.sync(); } } @Override public Map<Integer, EndpointAccessToken> getAttachEndpointRequests() { Map<Integer, EndpointAccessToken> result = new HashMap<>(); synchronized (attachEndpointRequests) { result.putAll(attachEndpointRequests); } return result; } @Override public Map<Integer, EndpointKeyHash> getDetachEndpointRequests() { Map<Integer, EndpointKeyHash> result = new HashMap<>(); synchronized (detachEndpointRequests) { result.putAll(detachEndpointRequests); } return result; } @Override public UserAttachRequest getUserAttachRequest() { return userAttachRequest; } @Override public boolean isAttachedToUser() { return state.isAttachedToUser(); } @Override public void setAttachedCallback(AttachEndpointToUserCallback listener) { this.attachEndpointToUserCallback = listener; } @Override public void setDetachedCallback(DetachEndpointFromUserCallback listener) { this.detachEndpointFromUserCallback = listener; } private synchronized int getRandomInt() { return RANDOM.nextInt(); } }