/*
* 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.server.operations.service.profile;
import org.kaaproject.kaa.common.avro.GenericAvroConverter;
import org.kaaproject.kaa.common.dto.EndpointProfileDto;
import org.kaaproject.kaa.common.dto.EndpointProfileSchemaDto;
import org.kaaproject.kaa.common.dto.EventClassFamilyVersionStateDto;
import org.kaaproject.kaa.common.dto.admin.SdkProfileDto;
import org.kaaproject.kaa.common.dto.credentials.EndpointRegistrationDto;
import org.kaaproject.kaa.common.dto.event.ApplicationEventFamilyMapDto;
import org.kaaproject.kaa.common.endpoint.security.KeyUtil;
import org.kaaproject.kaa.common.hash.EndpointObjectHash;
import org.kaaproject.kaa.common.hash.Sha1HashUtils;
import org.kaaproject.kaa.server.common.Base64Util;
import org.kaaproject.kaa.server.common.dao.EndpointRegistrationService;
import org.kaaproject.kaa.server.common.dao.EndpointService;
import org.kaaproject.kaa.server.common.dao.exception.EndpointRegistrationServiceException;
import org.kaaproject.kaa.server.common.dao.exception.KaaOptimisticLockingFailureException;
import org.kaaproject.kaa.server.operations.pojo.RegisterProfileRequest;
import org.kaaproject.kaa.server.operations.pojo.UpdateProfileRequest;
import org.kaaproject.kaa.server.operations.service.cache.AppSeqNumber;
import org.kaaproject.kaa.server.operations.service.cache.AppVersionKey;
import org.kaaproject.kaa.server.operations.service.cache.CacheService;
import org.kaaproject.kaa.server.operations.service.cache.EventClassFamilyIdKey;
import org.kaaproject.kaa.server.sync.ClientSyncMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* The Class DefaultProfileService is a default implementation of
* {@link ProfileService ProfileService}.
*
* @author ashvayka
*/
public class DefaultProfileService implements ProfileService {
private static final Logger LOG = LoggerFactory.getLogger(DefaultProfileService.class);
@Autowired
private EndpointService endpointService;
@Autowired
private EndpointRegistrationService endpointRegistrationService;
@Autowired
private CacheService cacheService;
@Override
public EndpointProfileDto getProfile(EndpointObjectHash endpointKey) {
return endpointService.findEndpointProfileByKeyHash(endpointKey.getData());
}
@Override
public EndpointProfileDto updateProfile(
EndpointProfileDto profile,
BiFunction<EndpointProfileDto, EndpointProfileDto, EndpointProfileDto> mergeFunction) {
return updateProfile(profile, mergeFunction, 3);
}
/*
* (non-Javadoc)
*
* @see org.kaaproject.kaa.server.operations.service.profile.ProfileService#
* updateProfile
* (org.kaaproject.kaa.server.operations.pojo.UpdateProfileRequest)
*/
@Override
public EndpointProfileDto updateProfile(UpdateProfileRequest request) {
LOG.debug("Updating Profile for {}", request.getEndpointKeyHash());
EndpointProfileDto dto = endpointService.findEndpointProfileByKeyHash(
request.getEndpointKeyHash().getData());
AppSeqNumber appSeqNumber = cacheService.getAppSeqNumber(request.getApplicationToken());
SdkProfileDto sdkProfile = cacheService.getSdkProfileBySdkToken(request.getSdkToken());
String profileJson = decodeProfile(
request.getProfile(), appSeqNumber.getAppToken(), sdkProfile.getProfileSchemaVersion());
Function<EndpointProfileDto, EndpointProfileDto> updateFunction = profile -> {
if (request.getAccessToken() != null) {
profile.setAccessToken(request.getAccessToken());
}
profile.setClientProfileBody(profileJson);
profile.setProfileHash(EndpointObjectHash.fromSha1(request.getProfile()).getData());
populateVersionStates(appSeqNumber.getTenantId(), profile, sdkProfile);
profile.setGroupState(new ArrayList<>());
profile.setSequenceNumber(0);
return profile;
};
return updateProfile(updateFunction.apply(dto), (storedProfile, newProfile) -> {
return updateFunction.apply(storedProfile);
});
}
@Override
public EndpointProfileDto updateProfile(ClientSyncMetaData metaData,
EndpointObjectHash keyHash,
boolean useConfigurationRawSchema) {
LOG.debug("Updating Profile for {}", keyHash);
EndpointProfileDto dto = endpointService.findEndpointProfileByKeyHash(keyHash.getData());
AppSeqNumber appSeqNumber = cacheService.getAppSeqNumber(metaData.getApplicationToken());
SdkProfileDto sdkProfile = cacheService.getSdkProfileBySdkToken(metaData.getSdkToken());
Function<EndpointProfileDto, EndpointProfileDto> updateFunction = profile -> {
populateVersionStates(appSeqNumber.getTenantId(), profile, sdkProfile);
profile.setGroupState(new ArrayList<>());
profile.setUseConfigurationRawSchema(useConfigurationRawSchema);
profile.setSequenceNumber(0);
return profile;
};
return updateProfile(updateFunction.apply(dto), (storedProfile, newProfile) -> {
return updateFunction.apply(storedProfile);
});
}
private EndpointProfileDto updateProfile(
EndpointProfileDto update,
BiFunction<EndpointProfileDto, EndpointProfileDto, EndpointProfileDto> mergeFunction,
int retryCount) {
LOG.debug("Updating profile {} ", update);
try {
return endpointService.saveEndpointProfile(update);
} catch (KaaOptimisticLockingFailureException ex) {
LOG.warn("Failed to update profile {} ", update, ex);
if (retryCount > 0) {
EndpointProfileDto stored = endpointService.findEndpointProfileByKeyHash(
update.getEndpointKeyHash());
LOG.warn("Going to merge it with stored profile {}", stored);
EndpointProfileDto merged = mergeFunction.apply(stored, update);
LOG.warn("Merge result: {}", merged);
return updateProfile(merged, mergeFunction, retryCount - 1);
} else {
throw ex;
}
}
}
/*
* (non-Javadoc)
*
* @see org.kaaproject.kaa.server.operations.service.profile.ProfileService#
* registerProfile
* (org.kaaproject.kaa.server.operations.pojo.RegisterProfileRequest)
*/
@Override
public EndpointProfileDto registerProfile(RegisterProfileRequest request) {
String endpointId = Base64Util.encode(Sha1HashUtils.hashToBytes(request.getEndpointKey()));
LOG.debug("Registering Profile for {}", request.getEndpointKey());
LOG.trace("Lookup application by token: {}", request.getAppToken());
AppSeqNumber appSeqNumber = cacheService.getAppSeqNumber(request.getAppToken());
LOG.trace("Application by token: {} found: {}", request.getAppToken(), appSeqNumber);
SdkProfileDto sdkProfile = cacheService.getSdkProfileBySdkToken(request.getSdkToken());
LOG.trace("Sdk properties by sdk token: {} found: {}", request.getSdkToken(), sdkProfile);
String profileJson = decodeProfile(
request.getProfile(), appSeqNumber.getAppToken(), sdkProfile.getProfileSchemaVersion());
EndpointObjectHash keyHash = EndpointObjectHash.fromSha1(request.getEndpointKey());
EndpointProfileDto dto = endpointService.findEndpointProfileByKeyHash(keyHash.getData());
if (dto == null) {
dto = new EndpointProfileDto();
dto.setSdkToken(sdkProfile.getToken());
dto.setApplicationId(appSeqNumber.getAppId());
dto.setEndpointKey(request.getEndpointKey());
dto.setEndpointKeyHash(keyHash.getData());
dto.setClientProfileBody(profileJson);
dto.setProfileHash(EndpointObjectHash.fromSha1(request.getProfile()).getData());
try {
Optional<EndpointRegistrationDto> endpointRegistrationLookup =
endpointRegistrationService.findEndpointRegistrationByEndpointId(endpointId);
if (endpointRegistrationLookup.isPresent()) {
LOG.debug("Endpoint registration information found {}: {}",
dto.getEndpointKey(), endpointRegistrationLookup.get());
EndpointRegistrationDto endpointRegistration = endpointRegistrationLookup.get();
if (endpointRegistration.getServerProfileBody() != null
&& endpointRegistration.getServerProfileVersion() != null) {
dto.setServerProfileVersion(endpointRegistration.getServerProfileVersion());
dto.setServerProfileBody(endpointRegistration.getServerProfileBody());
}
} else {
LOG.debug("Endpoint registration information not found {}", dto.getEndpointKey());
}
} catch (EndpointRegistrationServiceException ex) {
LOG.error("Failed to lookup registration information for: {}. Reason: {}",
dto.getEndpointKey(), ex);
throw new RuntimeException(ex);
}
populateVersionStates(appSeqNumber.getTenantId(), dto, sdkProfile);
if (request.getAccessToken() != null) {
dto.setAccessToken(request.getAccessToken());
}
dto.setSequenceNumber(0);
try {
cacheService.putEndpointKey(keyHash, KeyUtil.getPublic(dto.getEndpointKey()));
} catch (InvalidKeyException ex) {
LOG.error("Can't generate public key for endpoint key: {}. Reason: {}",
dto.getEndpointKey(), ex);
throw new RuntimeException(ex);
}
return endpointService.saveEndpointProfile(dto);
} else {
return updateProfile(new UpdateProfileRequest(
request.getAppToken(), keyHash, request.getAccessToken(), request.getProfile(),
request.getSdkToken()));
}
}
protected void populateVersionStates(String tenantId,
EndpointProfileDto dto,
SdkProfileDto sdkProfile) {
dto.setClientProfileVersion(sdkProfile.getProfileSchemaVersion());
dto.setConfigurationVersion(sdkProfile.getConfigurationSchemaVersion());
dto.setUserNfVersion(sdkProfile.getNotificationSchemaVersion());
dto.setLogSchemaVersion(sdkProfile.getLogSchemaVersion());
if (sdkProfile.getAefMapIds() != null) {
List<ApplicationEventFamilyMapDto> aefMaps = cacheService.getApplicationEventFamilyMapsByIds(
sdkProfile.getAefMapIds());
List<EventClassFamilyVersionStateDto> ecfVersionStates = new ArrayList<>(aefMaps.size());
for (ApplicationEventFamilyMapDto aefMap : aefMaps) {
EventClassFamilyVersionStateDto ecfVersionDto = new EventClassFamilyVersionStateDto();
String ecfId = cacheService.getEventClassFamilyIdByName(new EventClassFamilyIdKey(
tenantId, aefMap.getEcfName()));
if (ecfId != null) {
ecfVersionDto.setEcfId(ecfId);
ecfVersionDto.setVersion(aefMap.getVersion());
ecfVersionStates.add(ecfVersionDto);
} else {
LOG.warn("Failed to add ecf version state for ecf name {} and version {}",
aefMap.getEcfName(), aefMap.getVersion());
}
}
dto.setEcfVersionStates(ecfVersionStates);
}
}
private String decodeProfile(byte[] profileRaw, String appToken, int schemaVersion) {
LOG.trace("Lookup profileSchema by appToken: {} and version: {}", appToken, schemaVersion);
EndpointProfileSchemaDto profileSchemaDto = cacheService
.getProfileSchemaByAppAndVersion(new AppVersionKey(appToken, schemaVersion));
String profileSchema = cacheService.getFlatCtlSchemaById(profileSchemaDto.getCtlSchemaId());
LOG.trace("EndpointProfileSchema by appToken: {} and version: {} found: {}",
appToken, schemaVersion, profileSchema);
String profileJson = GenericAvroConverter.toJson(profileRaw, profileSchema);
LOG.trace("Profile json : {} ", profileJson);
return profileJson;
}
}