167 changed files with 4076 additions and 1441 deletions
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.service.install; |
|||
|
|||
import org.springframework.context.annotation.Profile; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.dao.util.NoSqlAnyDaoNonCloud; |
|||
|
|||
/* |
|||
* Create keyspace for Cassandra NoSQL database for non-cloud deployment. |
|||
* For cloud service like Astra DBaas admin have to create keyspace manually on cloud UI. |
|||
* Then create tokens with database admin role and put it on Thingsboard parameters. |
|||
* Without this service cloud DB will end up with exception like |
|||
* UnauthorizedException: Missing correct permission on thingsboard |
|||
* */ |
|||
@Service |
|||
@NoSqlAnyDaoNonCloud |
|||
@Profile("install") |
|||
public class CassandraKeyspaceService extends CassandraAbstractDatabaseSchemaService |
|||
implements NoSqlKeyspaceService { |
|||
public CassandraKeyspaceService() { |
|||
super("schema-keyspace.cql"); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.service.install; |
|||
|
|||
import org.springframework.context.annotation.Profile; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
|
|||
@Component |
|||
@Profile("install") |
|||
public class DbUpgradeExecutorService extends DbCallbackExecutorService { |
|||
|
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.service.install; |
|||
|
|||
public interface NoSqlKeyspaceService extends DatabaseSchemaService { |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.service.security.auth; |
|||
|
|||
import io.jsonwebtoken.Claims; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.beans.factory.annotation.Qualifier; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.TbTransactionalCache; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; |
|||
import org.thingsboard.server.common.data.security.model.JwtToken; |
|||
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
import static java.util.concurrent.TimeUnit.MILLISECONDS; |
|||
|
|||
@Service |
|||
public class DefaultTokenOutdatingService implements TokenOutdatingService { |
|||
|
|||
private final TbTransactionalCache<String, Long> cache; |
|||
private final JwtTokenFactory tokenFactory; |
|||
|
|||
public DefaultTokenOutdatingService(@Qualifier("UsersSessionInvalidation") TbTransactionalCache<String, Long> cache, JwtTokenFactory tokenFactory) { |
|||
this.cache = cache; |
|||
this.tokenFactory = tokenFactory; |
|||
} |
|||
|
|||
@EventListener(classes = UserAuthDataChangedEvent.class) |
|||
public void onUserAuthDataChanged(UserAuthDataChangedEvent event) { |
|||
if (StringUtils.hasText(event.getId())) { |
|||
cache.put(event.getId(), event.getTs()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean isOutdated(JwtToken token, UserId userId) { |
|||
Claims claims = tokenFactory.parseTokenClaims(token).getBody(); |
|||
long issueTime = claims.getIssuedAt().getTime(); |
|||
String sessionId = claims.get("sessionId", String.class); |
|||
if (isTokenOutdated(issueTime, userId.toString())){ |
|||
return true; |
|||
} else { |
|||
return sessionId != null && isTokenOutdated(issueTime, sessionId); |
|||
} |
|||
} |
|||
|
|||
private Boolean isTokenOutdated(long issueTime, String sessionId) { |
|||
return Optional.ofNullable(cache.get(sessionId)).map(outdatageTime -> isTokenOutdated(issueTime, outdatageTime.get())).orElse(false); |
|||
} |
|||
|
|||
private boolean isTokenOutdated(long issueTime, Long outdatageTime) { |
|||
return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime); |
|||
} |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.service.entitiy.alarm; |
|||
|
|||
import com.google.common.util.concurrent.Futures; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.springframework.boot.test.mock.mockito.MockBean; |
|||
import org.springframework.boot.test.mock.mockito.SpyBean; |
|||
import org.springframework.test.context.ContextConfiguration; |
|||
import org.springframework.test.context.TestPropertySource; |
|||
import org.springframework.test.context.junit4.SpringRunner; |
|||
import org.thingsboard.server.cluster.TbClusterService; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.customer.CustomerService; |
|||
import org.thingsboard.server.dao.edge.EdgeService; |
|||
import org.thingsboard.server.service.entitiy.TbNotificationEntityService; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; |
|||
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; |
|||
|
|||
import static org.mockito.ArgumentMatchers.any; |
|||
import static org.mockito.ArgumentMatchers.anyLong; |
|||
import static org.mockito.ArgumentMatchers.anyString; |
|||
import static org.mockito.ArgumentMatchers.eq; |
|||
import static org.mockito.Mockito.times; |
|||
import static org.mockito.Mockito.verify; |
|||
import static org.mockito.Mockito.when; |
|||
|
|||
@Slf4j |
|||
@RunWith(SpringRunner.class) |
|||
@ContextConfiguration(classes = DefaultTbAlarmService.class) |
|||
@TestPropertySource(properties = { |
|||
"server.log_controller_error_stack_trace=false" |
|||
}) |
|||
public class DefaultTbAlarmServiceTest { |
|||
|
|||
@MockBean |
|||
protected DbCallbackExecutorService dbExecutor; |
|||
@MockBean |
|||
protected TbNotificationEntityService notificationEntityService; |
|||
@MockBean |
|||
protected EdgeService edgeService; |
|||
@MockBean |
|||
protected AlarmService alarmService; |
|||
@MockBean |
|||
protected AlarmSubscriptionService alarmSubscriptionService; |
|||
@MockBean |
|||
protected CustomerService customerService; |
|||
@MockBean |
|||
protected TbClusterService tbClusterService; |
|||
@MockBean |
|||
private EntitiesVersionControlService vcService; |
|||
|
|||
@SpyBean |
|||
DefaultTbAlarmService service; |
|||
|
|||
@Test |
|||
public void testSave() throws ThingsboardException { |
|||
var alarm = new Alarm(); |
|||
when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm); |
|||
service.save(alarm, new User()); |
|||
|
|||
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); |
|||
verify(alarmSubscriptionService, times(1)).createOrUpdateAlarm(eq(alarm)); |
|||
} |
|||
|
|||
@Test |
|||
public void testAck() { |
|||
var alarm = new Alarm(); |
|||
alarm.setStatus(AlarmStatus.ACTIVE_UNACK); |
|||
when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); |
|||
service.ack(alarm, new User()); |
|||
|
|||
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); |
|||
verify(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong()); |
|||
} |
|||
|
|||
@Test |
|||
public void testClear() { |
|||
var alarm = new Alarm(); |
|||
alarm.setStatus(AlarmStatus.ACTIVE_ACK); |
|||
when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); |
|||
service.clear(alarm, new User()); |
|||
|
|||
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); |
|||
verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDelete() { |
|||
service.delete(new Alarm(), new User()); |
|||
|
|||
verify(notificationEntityService, times(1)).notifyDeleteAlarm(any(), any(), any(), any(), any(), any(), anyString()); |
|||
verify(alarmSubscriptionService, times(1)).deleteAlarm(any(), any()); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.cache.usersUpdateTime; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.cache.CacheManager; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.CaffeineTbTransactionalCache; |
|||
import org.thingsboard.server.common.data.CacheConstants; |
|||
|
|||
|
|||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) |
|||
@Service("UsersSessionInvalidation") |
|||
public class UsersSessionInvalidationCaffeineCache extends CaffeineTbTransactionalCache<String, Long> { |
|||
|
|||
@Autowired |
|||
public UsersSessionInvalidationCaffeineCache(CacheManager cacheManager) { |
|||
super(cacheManager, CacheConstants.USERS_SESSION_INVALIDATION_CACHE); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.cache.usersUpdateTime; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.data.redis.connection.RedisConnectionFactory; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.CacheSpecsMap; |
|||
import org.thingsboard.server.cache.RedisTbTransactionalCache; |
|||
import org.thingsboard.server.cache.TBRedisCacheConfiguration; |
|||
import org.thingsboard.server.cache.TbFSTRedisSerializer; |
|||
import org.thingsboard.server.common.data.CacheConstants; |
|||
|
|||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") |
|||
@Service("UsersSessionInvalidation") |
|||
public class UsersSessionInvalidationRedisCache extends RedisTbTransactionalCache<String, Long> { |
|||
|
|||
@Autowired |
|||
public UsersSessionInvalidationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { |
|||
super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.dao.util; |
|||
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
|
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
|
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@ConditionalOnExpression("('${database.ts.type}'=='cassandra' || '${database.ts_latest.type}'=='cassandra') " + |
|||
"&& ('${cassandra.cloud.secure_connect_bundle_path}' == null || '${cassandra.cloud.secure_connect_bundle_path}'.isBlank() )") |
|||
public @interface NoSqlAnyDaoNonCloud { |
|||
} |
|||
@ -0,0 +1,301 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.common.data; |
|||
|
|||
import com.github.os72.protobuf.dynamic.DynamicSchema; |
|||
import com.github.os72.protobuf.dynamic.EnumDefinition; |
|||
import com.github.os72.protobuf.dynamic.MessageDefinition; |
|||
import com.google.protobuf.Descriptors; |
|||
import com.google.protobuf.DynamicMessage; |
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
import com.google.protobuf.util.JsonFormat; |
|||
import com.squareup.wire.Syntax; |
|||
import com.squareup.wire.schema.Field; |
|||
import com.squareup.wire.schema.Location; |
|||
import com.squareup.wire.schema.internal.parser.EnumConstantElement; |
|||
import com.squareup.wire.schema.internal.parser.EnumElement; |
|||
import com.squareup.wire.schema.internal.parser.FieldElement; |
|||
import com.squareup.wire.schema.internal.parser.MessageElement; |
|||
import com.squareup.wire.schema.internal.parser.OneOfElement; |
|||
import com.squareup.wire.schema.internal.parser.ProtoFileElement; |
|||
import com.squareup.wire.schema.internal.parser.ProtoParser; |
|||
import com.squareup.wire.schema.internal.parser.TypeElement; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public class DynamicProtoUtils { |
|||
|
|||
public static final Location LOCATION = new Location("", "", -1, -1); |
|||
public static final String PROTO_3_SYNTAX = "proto3"; |
|||
|
|||
public static Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { |
|||
try { |
|||
DynamicMessage.Builder builder = getDynamicMessageBuilder(protoSchema, schemaName); |
|||
return builder.getDescriptorForType(); |
|||
} catch (Exception e) { |
|||
log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static DynamicMessage.Builder getDynamicMessageBuilder(String protoSchema, String schemaName) { |
|||
ProtoFileElement protoFileElement = getProtoFileElement(protoSchema); |
|||
DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); |
|||
String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() |
|||
.map(MessageElement::getName).reduce((previous, last) -> last).get(); |
|||
return dynamicSchema.newMessageBuilder(lastMsgName); |
|||
} |
|||
|
|||
public static DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { |
|||
DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); |
|||
schemaBuilder.setName(schemaName); |
|||
schemaBuilder.setSyntax(PROTO_3_SYNTAX); |
|||
schemaBuilder.setPackage(StringUtils.isNotEmpty(protoFileElement.getPackageName()) ? |
|||
protoFileElement.getPackageName() : schemaName.toLowerCase()); |
|||
List<TypeElement> types = protoFileElement.getTypes(); |
|||
List<MessageElement> messageTypes = getMessageTypes(types); |
|||
|
|||
if (!messageTypes.isEmpty()) { |
|||
List<EnumElement> enumTypes = getEnumElements(types); |
|||
if (!enumTypes.isEmpty()) { |
|||
enumTypes.forEach(enumElement -> { |
|||
EnumDefinition enumDefinition = getEnumDefinition(enumElement); |
|||
schemaBuilder.addEnumDefinition(enumDefinition); |
|||
}); |
|||
} |
|||
List<MessageDefinition> messageDefinitions = getMessageDefinitions(messageTypes); |
|||
messageDefinitions.forEach(schemaBuilder::addMessageDefinition); |
|||
try { |
|||
return schemaBuilder.build(); |
|||
} catch (Descriptors.DescriptorValidationException e) { |
|||
throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); |
|||
} |
|||
} else { |
|||
throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); |
|||
} |
|||
} |
|||
|
|||
public static ProtoFileElement getProtoFileElement(String protoSchema) { |
|||
return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); |
|||
} |
|||
|
|||
public static String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { |
|||
DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); |
|||
return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); |
|||
} |
|||
|
|||
public static DynamicMessage jsonToDynamicMessage(DynamicMessage.Builder builder, String payload) throws InvalidProtocolBufferException { |
|||
JsonFormat.parser().ignoringUnknownFields().merge(payload, builder); |
|||
return builder.build(); |
|||
} |
|||
|
|||
private static List<MessageElement> getMessageTypes(List<TypeElement> types) { |
|||
return types.stream() |
|||
.filter(typeElement -> typeElement instanceof MessageElement) |
|||
.map(typeElement -> (MessageElement) typeElement) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
private static List<EnumElement> getEnumElements(List<TypeElement> types) { |
|||
return types.stream() |
|||
.filter(typeElement -> typeElement instanceof EnumElement) |
|||
.map(typeElement -> (EnumElement) typeElement) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
private static List<MessageDefinition> getMessageDefinitions(List<MessageElement> messageElementsList) { |
|||
if (!messageElementsList.isEmpty()) { |
|||
List<MessageDefinition> messageDefinitions = new ArrayList<>(); |
|||
messageElementsList.forEach(messageElement -> { |
|||
MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); |
|||
|
|||
List<TypeElement> nestedTypes = messageElement.getNestedTypes(); |
|||
if (!nestedTypes.isEmpty()) { |
|||
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes); |
|||
if (!nestedEnumTypes.isEmpty()) { |
|||
nestedEnumTypes.forEach(enumElement -> { |
|||
EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); |
|||
messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); |
|||
}); |
|||
} |
|||
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes); |
|||
List<MessageDefinition> nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); |
|||
nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); |
|||
} |
|||
List<FieldElement> messageElementFields = messageElement.getFields(); |
|||
List<OneOfElement> oneOfs = messageElement.getOneOfs(); |
|||
if (!oneOfs.isEmpty()) { |
|||
for (OneOfElement oneOfelement : oneOfs) { |
|||
MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); |
|||
addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); |
|||
} |
|||
} |
|||
if (!messageElementFields.isEmpty()) { |
|||
addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); |
|||
} |
|||
messageDefinitions.add(messageDefinitionBuilder.build()); |
|||
}); |
|||
return messageDefinitions; |
|||
} else { |
|||
return Collections.emptyList(); |
|||
} |
|||
} |
|||
|
|||
private static EnumDefinition getEnumDefinition(EnumElement enumElement) { |
|||
List<EnumConstantElement> enumElementTypeConstants = enumElement.getConstants(); |
|||
EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); |
|||
if (!enumElementTypeConstants.isEmpty()) { |
|||
enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); |
|||
} |
|||
return enumDefinitionBuilder.build(); |
|||
} |
|||
|
|||
|
|||
private static void addMessageFieldsToTheMessageDefinition(List<FieldElement> messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { |
|||
messageElementFields.forEach(fieldElement -> { |
|||
String labelStr = null; |
|||
if (fieldElement.getLabel() != null) { |
|||
labelStr = fieldElement.getLabel().name().toLowerCase(); |
|||
} |
|||
messageDefinitionBuilder.addField( |
|||
labelStr, |
|||
fieldElement.getType(), |
|||
fieldElement.getName(), |
|||
fieldElement.getTag()); |
|||
}); |
|||
} |
|||
|
|||
private static void addMessageFieldsToTheOneOfDefinition(List<FieldElement> oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { |
|||
oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( |
|||
fieldElement.getType(), |
|||
fieldElement.getName(), |
|||
fieldElement.getTag())); |
|||
oneofBuilder.msgDefBuilder(); |
|||
} |
|||
|
|||
// validation
|
|||
|
|||
public static void validateProtoSchema(String schema, String schemaName, String exceptionPrefix) throws IllegalArgumentException { |
|||
ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); |
|||
ProtoFileElement protoFileElement; |
|||
try { |
|||
protoFileElement = schemaParser.readProtoFile(); |
|||
} catch (Exception e) { |
|||
throw new IllegalArgumentException(exceptionPrefix + " failed to parse " + schemaName + " due to: " + e.getMessage()); |
|||
} |
|||
checkProtoFileSyntax(schemaName, protoFileElement); |
|||
checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!", exceptionPrefix); |
|||
checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!", exceptionPrefix); |
|||
checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!", exceptionPrefix); |
|||
checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!", exceptionPrefix); |
|||
checkTypeElements(schemaName, protoFileElement, exceptionPrefix); |
|||
} |
|||
|
|||
private static void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { |
|||
if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { |
|||
throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + |
|||
" for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); |
|||
} |
|||
} |
|||
|
|||
private static void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage, String exceptionPrefix) { |
|||
if (!isEmptySettings) { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + invalidSettingsMessage); |
|||
} |
|||
} |
|||
|
|||
private static void checkTypeElements(String schemaName, ProtoFileElement protoFileElement, String exceptionPrefix) { |
|||
List<TypeElement> types = protoFileElement.getTypes(); |
|||
if (!types.isEmpty()) { |
|||
if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " At least one Message definition should exists!"); |
|||
} else { |
|||
checkEnumElements(schemaName, getEnumElements(types), exceptionPrefix); |
|||
checkMessageElements(schemaName, getMessageTypes(types), exceptionPrefix); |
|||
} |
|||
} else { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Type elements is empty!"); |
|||
} |
|||
} |
|||
|
|||
private static void checkFieldElements(String schemaName, List<FieldElement> fieldElements, String exceptionPrefix) { |
|||
if (!fieldElements.isEmpty()) { |
|||
boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { |
|||
Field.Label label = fieldElement.getLabel(); |
|||
return label != null && label.equals(Field.Label.REQUIRED); |
|||
}); |
|||
if (hasRequiredLabel) { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Required labels are not supported!"); |
|||
} |
|||
boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); |
|||
if (hasDefaultValue) { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Default values are not supported!"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void checkEnumElements(String schemaName, List<EnumElement> enumTypes, String exceptionPrefix) { |
|||
if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Nested types in Enum definitions are not supported!"); |
|||
} |
|||
if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { |
|||
throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Enum definitions options are not supported!"); |
|||
} |
|||
} |
|||
|
|||
private static void checkMessageElements(String schemaName, List<MessageElement> messageElementsList, String exceptionPrefix) { |
|||
if (!messageElementsList.isEmpty()) { |
|||
messageElementsList.forEach(messageElement -> { |
|||
checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), |
|||
" Message definition groups don't support!", exceptionPrefix); |
|||
checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), |
|||
" Message definition options don't support!", exceptionPrefix); |
|||
checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), |
|||
" Message definition extensions don't support!", exceptionPrefix); |
|||
checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), |
|||
" Message definition reserved elements don't support!", exceptionPrefix); |
|||
checkFieldElements(schemaName, messageElement.getFields(), exceptionPrefix); |
|||
List<OneOfElement> oneOfs = messageElement.getOneOfs(); |
|||
if (!oneOfs.isEmpty()) { |
|||
oneOfs.forEach(oneOfElement -> { |
|||
checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), |
|||
" OneOf definition groups don't support!", exceptionPrefix); |
|||
checkFieldElements(schemaName, oneOfElement.getFields(), exceptionPrefix); |
|||
}); |
|||
} |
|||
List<TypeElement> nestedTypes = messageElement.getNestedTypes(); |
|||
if (!nestedTypes.isEmpty()) { |
|||
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes); |
|||
if (!nestedEnumTypes.isEmpty()) { |
|||
checkEnumElements(schemaName, nestedEnumTypes, exceptionPrefix); |
|||
} |
|||
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes); |
|||
checkMessageElements(schemaName, nestedMessageTypes, exceptionPrefix); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public static String invalidSchemaProvidedMessage(String schemaName, String exceptionPrefix) { |
|||
return exceptionPrefix + " invalid " + schemaName + " provided!"; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.common.data.security.event; |
|||
|
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
|
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class UserCredentialsInvalidationEvent extends UserAuthDataChangedEvent { |
|||
private final UserId userId; |
|||
private final long ts; |
|||
|
|||
public UserCredentialsInvalidationEvent(UserId userId) { |
|||
this.userId = userId; |
|||
this.ts = System.currentTimeMillis(); |
|||
} |
|||
|
|||
@Override |
|||
public String getId() { |
|||
return userId.toString(); |
|||
} |
|||
|
|||
@Override |
|||
public long getTs() { |
|||
return ts; |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.common.data.security.event; |
|||
|
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class UserSessionInvalidationEvent extends UserAuthDataChangedEvent { |
|||
private final String sessionId; |
|||
private final long ts; |
|||
|
|||
public UserSessionInvalidationEvent(String sessionId) { |
|||
this.sessionId = sessionId; |
|||
this.ts = System.currentTimeMillis(); |
|||
} |
|||
|
|||
@Override |
|||
public String getId() { |
|||
return sessionId; |
|||
} |
|||
|
|||
@Override |
|||
public long getTs() { |
|||
return ts; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.common.data.util; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
public class TbPair<S, T> { |
|||
private S first; |
|||
private T second; |
|||
} |
|||
@ -0,0 +1,169 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.common.data; |
|||
|
|||
import com.github.os72.protobuf.dynamic.DynamicSchema; |
|||
import com.google.protobuf.Descriptors; |
|||
import com.google.protobuf.DynamicMessage; |
|||
import com.squareup.wire.schema.internal.parser.ProtoFileElement; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.mockito.junit.MockitoJUnitRunner; |
|||
|
|||
import java.util.List; |
|||
import java.util.Set; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
@RunWith(MockitoJUnitRunner.class) |
|||
public class DynamicProtoUtilsTest { |
|||
|
|||
@Test |
|||
public void testProtoSchemaWithMessageNestedTypes() throws Exception { |
|||
String schema = "syntax = \"proto3\";\n" + |
|||
"\n" + |
|||
"package testnested;\n" + |
|||
"\n" + |
|||
"message Outer {\n" + |
|||
" message MiddleAA {\n" + |
|||
" message Inner {\n" + |
|||
" optional int64 ival = 1;\n" + |
|||
" optional bool booly = 2;\n" + |
|||
" }\n" + |
|||
" Inner inner = 1;\n" + |
|||
" }\n" + |
|||
" message MiddleBB {\n" + |
|||
" message Inner {\n" + |
|||
" optional int32 ival = 1;\n" + |
|||
" optional bool booly = 2;\n" + |
|||
" }\n" + |
|||
" Inner inner = 1;\n" + |
|||
" }\n" + |
|||
" MiddleAA middleAA = 1;\n" + |
|||
" MiddleBB middleBB = 2;\n" + |
|||
"}"; |
|||
ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(schema); |
|||
DynamicSchema dynamicSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, "test schema with nested types"); |
|||
assertNotNull(dynamicSchema); |
|||
Set<String> messageTypes = dynamicSchema.getMessageTypes(); |
|||
assertEquals(5, messageTypes.size()); |
|||
assertTrue(messageTypes.contains("testnested.Outer")); |
|||
assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); |
|||
assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); |
|||
assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); |
|||
assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); |
|||
|
|||
DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); |
|||
Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); |
|||
DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder |
|||
.setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) |
|||
.setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) |
|||
.build(); |
|||
|
|||
DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); |
|||
Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); |
|||
DynamicMessage middleAAMsg = middleAAMsgBuilder |
|||
.setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) |
|||
.build(); |
|||
|
|||
DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); |
|||
Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); |
|||
DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder |
|||
.setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) |
|||
.setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) |
|||
.build(); |
|||
|
|||
DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); |
|||
Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); |
|||
DynamicMessage middleBBMsg = middleBBMsgBuilder |
|||
.setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) |
|||
.build(); |
|||
|
|||
|
|||
DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); |
|||
Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); |
|||
DynamicMessage outerMsg = outerMsgBuilder |
|||
.setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) |
|||
.setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) |
|||
.build(); |
|||
|
|||
assertEquals("{\n" + |
|||
" \"middleAA\": {\n" + |
|||
" \"inner\": {\n" + |
|||
" \"ival\": \"1\",\n" + |
|||
" \"booly\": true\n" + |
|||
" }\n" + |
|||
" },\n" + |
|||
" \"middleBB\": {\n" + |
|||
" \"inner\": {\n" + |
|||
" \"ival\": 0,\n" + |
|||
" \"booly\": false\n" + |
|||
" }\n" + |
|||
" }\n" + |
|||
"}", DynamicProtoUtils.dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); |
|||
} |
|||
|
|||
@Test |
|||
public void testProtoSchemaWithMessageOneOfs() throws Exception { |
|||
String schema = "syntax = \"proto3\";\n" + |
|||
"\n" + |
|||
"package testoneofs;\n" + |
|||
"\n" + |
|||
"message SubMessage {\n" + |
|||
" repeated string name = 1;\n" + |
|||
"}\n" + |
|||
"\n" + |
|||
"message SampleMessage {\n" + |
|||
" optional int32 id = 1;\n" + |
|||
" oneof testOneOf {\n" + |
|||
" string name = 4;\n" + |
|||
" SubMessage subMessage = 9;\n" + |
|||
" }\n" + |
|||
"}"; |
|||
ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(schema); |
|||
DynamicSchema dynamicSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, "test schema with message oneOfs"); |
|||
assertNotNull(dynamicSchema); |
|||
Set<String> messageTypes = dynamicSchema.getMessageTypes(); |
|||
assertEquals(2, messageTypes.size()); |
|||
assertTrue(messageTypes.contains("testoneofs.SubMessage")); |
|||
assertTrue(messageTypes.contains("testoneofs.SampleMessage")); |
|||
|
|||
DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); |
|||
Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); |
|||
assertNotNull(sampleMsgDescriptor); |
|||
|
|||
List<Descriptors.FieldDescriptor> fields = sampleMsgDescriptor.getFields(); |
|||
assertEquals(3, fields.size()); |
|||
DynamicMessage sampleMsg = sampleMsgBuilder |
|||
.setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") |
|||
.build(); |
|||
assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", DynamicProtoUtils.dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); |
|||
|
|||
DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); |
|||
Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); |
|||
DynamicMessage subMsg = subMsgBuilder |
|||
.addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") |
|||
.addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") |
|||
.build(); |
|||
|
|||
DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); |
|||
assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", |
|||
DynamicProtoUtils.dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.server.common.msg; |
|||
|
|||
import org.junit.jupiter.api.Test; |
|||
import org.thingsboard.server.common.data.FSTUtils; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.RuleNodeId; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
class TbMsgProcessingStackItemTest { |
|||
|
|||
@Test |
|||
void testSerialization() { |
|||
TbMsgProcessingStackItem item = new TbMsgProcessingStackItem(new RuleChainId(UUID.randomUUID()), new RuleNodeId(UUID.randomUUID())); |
|||
byte[] bytes = FSTUtils.encode(item); |
|||
TbMsgProcessingStackItem itemDecoded = FSTUtils.decode(bytes); |
|||
assertThat(item).isEqualTo(itemDecoded); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.script.api.mvel; |
|||
|
|||
import java.text.DateFormat; |
|||
import java.text.SimpleDateFormat; |
|||
import java.util.Date; |
|||
import java.util.GregorianCalendar; |
|||
import java.util.Locale; |
|||
import java.util.TimeZone; |
|||
|
|||
public class TbDate extends Date { |
|||
|
|||
public TbDate() { |
|||
super(); |
|||
} |
|||
|
|||
public TbDate(String s) { |
|||
super(parse(s)); |
|||
} |
|||
|
|||
public TbDate(long date) { |
|||
super(date); |
|||
} |
|||
|
|||
public TbDate(int year, int month, int date) { |
|||
this(year, month, date, 0, 0, 0); |
|||
} |
|||
|
|||
public TbDate(int year, int month, int date, int hrs, int min) { |
|||
this(year, month, date, hrs, min, 0); |
|||
} |
|||
|
|||
public TbDate(int year, int month, int date, |
|||
int hrs, int min, int second) { |
|||
super(new GregorianCalendar(year, month, date, hrs, min, second).getTimeInMillis()); |
|||
} |
|||
|
|||
public String toDateString() { |
|||
DateFormat formatter = DateFormat.getDateInstance(); |
|||
return formatter.format(this); |
|||
} |
|||
|
|||
public String toTimeString() { |
|||
DateFormat formatter = DateFormat.getTimeInstance(DateFormat.LONG); |
|||
return formatter.format(this); |
|||
} |
|||
|
|||
public String toISOString() { |
|||
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sssZ"); |
|||
return formatter.format(this); |
|||
} |
|||
|
|||
public String toLocaleString(String locale) { |
|||
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); |
|||
return formatter.format(this); |
|||
} |
|||
|
|||
public String toLocaleString(String locale, String tz) { |
|||
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); |
|||
formatter.setTimeZone(TimeZone.getTimeZone(tz)); |
|||
return formatter.format(this); |
|||
} |
|||
|
|||
public static long now() { |
|||
return System.currentTimeMillis(); |
|||
} |
|||
|
|||
public static long parse(String value) { |
|||
try { |
|||
return Date.parse(value); |
|||
} catch (IllegalArgumentException e) { |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
public static long UTC(int year, int month, int date, |
|||
int hrs, int min, int sec) { |
|||
return Date.UTC(year - 1900, month, date, hrs, min, sec); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* 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.thingsboard.common.util; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.junit.Assert; |
|||
import org.junit.Test; |
|||
|
|||
public class JacksonUtilTest { |
|||
|
|||
@Test |
|||
public void allow_unquoted_field_mapper_test() { |
|||
String data = "{data: 123}"; |
|||
JsonNode actualResult = JacksonUtil.toJsonNode(data, JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); // should be: {"data": 123}
|
|||
ObjectNode expectedResult = JacksonUtil.newObjectNode(); |
|||
expectedResult.put("data", 123); // {"data": 123}
|
|||
Assert.assertEquals(expectedResult, actualResult); |
|||
Assert.assertThrows(IllegalArgumentException.class, () -> JacksonUtil.toJsonNode(data)); // syntax exception due to missing quotes in the field name!
|
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue