275 changed files with 8569 additions and 8844 deletions
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
@ -0,0 +1,126 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.subscription; |
||||
|
|
||||
|
import ch.qos.logback.classic.Logger; |
||||
|
import ch.qos.logback.classic.spi.ILoggingEvent; |
||||
|
import ch.qos.logback.core.read.ListAppender; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import com.google.common.util.concurrent.ListeningExecutorService; |
||||
|
import com.google.common.util.concurrent.MoreExecutors; |
||||
|
import org.apache.commons.lang3.RandomStringUtils; |
||||
|
import org.junit.jupiter.api.AfterEach; |
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.test.util.ReflectionTestUtils; |
||||
|
import org.thingsboard.server.cache.limits.RateLimitService; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.limit.LimitedApi; |
||||
|
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.queue.discovery.PartitionService; |
||||
|
import org.thingsboard.server.service.ws.WebSocketSessionRef; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
import static org.junit.jupiter.api.Assertions.assertFalse; |
||||
|
import static org.mockito.ArgumentMatchers.any; |
||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||
|
import static org.mockito.ArgumentMatchers.nullable; |
||||
|
import static org.mockito.Mockito.mock; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
public class DefaultTbLocalSubscriptionServiceTest { |
||||
|
|
||||
|
ListAppender<ILoggingEvent> testLogAppender; |
||||
|
TbLocalSubscriptionService subscriptionService; |
||||
|
|
||||
|
@BeforeEach |
||||
|
public void setUp() throws Exception { |
||||
|
Logger logger = (Logger) LoggerFactory.getLogger(DefaultTbLocalSubscriptionService.class); |
||||
|
testLogAppender = new ListAppender<>(); |
||||
|
testLogAppender.start(); |
||||
|
logger.addAppender(testLogAppender); |
||||
|
|
||||
|
RateLimitService rateLimitService = mock(); |
||||
|
when(rateLimitService.checkRateLimit(eq(LimitedApi.WS_SUBSCRIPTIONS), any(Object.class), nullable(String.class))).thenReturn(true); |
||||
|
PartitionService partitionService = mock(); |
||||
|
when(partitionService.resolve(any(), any(), any())).thenReturn(TopicPartitionInfo.builder().build()); |
||||
|
subscriptionService = new DefaultTbLocalSubscriptionService(mock(), mock(), mock(), partitionService, mock(), mock(), mock(), rateLimitService); |
||||
|
ReflectionTestUtils.setField(subscriptionService, "serviceId", "serviceId"); |
||||
|
} |
||||
|
|
||||
|
@AfterEach |
||||
|
public void tearDown() { |
||||
|
if (testLogAppender != null) { |
||||
|
testLogAppender.stop(); |
||||
|
Logger logger = (Logger) LoggerFactory.getLogger(DefaultTbLocalSubscriptionService.class); |
||||
|
logger.detachAppender(testLogAppender); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void addSubscriptionConcurrentModificationTest() throws Exception { |
||||
|
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); |
||||
|
TenantId tenantId = new TenantId(UUID.randomUUID()); |
||||
|
DeviceId deviceId = new DeviceId(UUID.randomUUID()); |
||||
|
WebSocketSessionRef sessionRef = mock(); |
||||
|
ReflectionTestUtils.setField(subscriptionService, "subscriptionUpdateExecutor", executorService); |
||||
|
|
||||
|
List<ListenableFuture<?>> futures = new ArrayList<>(); |
||||
|
|
||||
|
try { |
||||
|
subscriptionService.onCoreStartupMsg(TransportProtos.CoreStartupMsg.newBuilder().addAllPartitions(List.of(0)).getDefaultInstanceForType()); |
||||
|
for (int i = 0; i < 50; i++) { |
||||
|
futures.add(executorService.submit(() -> subscriptionService.addSubscription(createSubscription(tenantId, deviceId), sessionRef))); |
||||
|
} |
||||
|
Futures.allAsList(futures).get(); |
||||
|
} finally { |
||||
|
executorService.shutdownNow(); |
||||
|
} |
||||
|
|
||||
|
List<ILoggingEvent> logs = testLogAppender.list; |
||||
|
boolean exceptionLogged = logs.stream() |
||||
|
.filter(event -> event.getThrowableProxy() != null) |
||||
|
.map(event -> event.getThrowableProxy().getClassName()) |
||||
|
.anyMatch(log -> log.equals("java.util.ConcurrentModificationException")); |
||||
|
|
||||
|
assertFalse(exceptionLogged, "Detected ConcurrentModificationException!"); |
||||
|
} |
||||
|
|
||||
|
private TbSubscription<?> createSubscription(TenantId tenantId, EntityId entityId) { |
||||
|
Map<String, Long> keys = new HashMap<>(); |
||||
|
for (int i = 0; i < 50; i++) { |
||||
|
keys.put(RandomStringUtils.randomAlphanumeric(5), 1L); |
||||
|
} |
||||
|
return TbAttributeSubscription.builder() |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.subscriptionId(1) |
||||
|
.sessionId(RandomStringUtils.randomAlphanumeric(5)) |
||||
|
.keyStates(keys) |
||||
|
.build(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,186 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.subscription; |
||||
|
|
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
import java.util.HashSet; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static org.junit.jupiter.api.Assertions.assertEquals; |
||||
|
import static org.junit.jupiter.api.Assertions.assertFalse; |
||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull; |
||||
|
import static org.junit.jupiter.api.Assertions.assertNull; |
||||
|
import static org.junit.jupiter.api.Assertions.assertTrue; |
||||
|
|
||||
|
public class TbEntityLocalSubsInfoTest { |
||||
|
|
||||
|
@Test |
||||
|
public void addTest() { |
||||
|
Set<TbAttributeSubscription> expectedSubs = new HashSet<>(); |
||||
|
TbEntityLocalSubsInfo subsInfo = createSubsInfo(); |
||||
|
TenantId tenantId = subsInfo.getTenantId(); |
||||
|
EntityId entityId = subsInfo.getEntityId(); |
||||
|
TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session1") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key1", 1L, "key2", 2L)) |
||||
|
.build(); |
||||
|
expectedSubs.add(attrSubscription1); |
||||
|
TbEntitySubEvent created = subsInfo.add(attrSubscription1); |
||||
|
assertFalse(subsInfo.isEmpty()); |
||||
|
assertNotNull(created); |
||||
|
assertEquals(expectedSubs, subsInfo.getSubs()); |
||||
|
checkEvent(created, expectedSubs, ComponentLifecycleEvent.CREATED); |
||||
|
|
||||
|
assertNull(subsInfo.add(attrSubscription1)); |
||||
|
|
||||
|
TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session2") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key3", 3L, "key4", 4L)) |
||||
|
.build(); |
||||
|
expectedSubs.add(attrSubscription2); |
||||
|
TbEntitySubEvent updated = subsInfo.add(attrSubscription2); |
||||
|
assertNotNull(updated); |
||||
|
|
||||
|
assertEquals(expectedSubs, subsInfo.getSubs()); |
||||
|
checkEvent(updated, expectedSubs, ComponentLifecycleEvent.UPDATED); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void removeTest() { |
||||
|
Set<TbAttributeSubscription> expectedSubs = new HashSet<>(); |
||||
|
TbEntityLocalSubsInfo subsInfo = createSubsInfo(); |
||||
|
TenantId tenantId = subsInfo.getTenantId(); |
||||
|
EntityId entityId = subsInfo.getEntityId(); |
||||
|
TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session1") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key1", 1L, "key2", 2L)) |
||||
|
.build(); |
||||
|
|
||||
|
TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session2") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key3", 3L, "key4", 4L)) |
||||
|
.build(); |
||||
|
|
||||
|
expectedSubs.add(attrSubscription1); |
||||
|
expectedSubs.add(attrSubscription2); |
||||
|
|
||||
|
subsInfo.add(attrSubscription1); |
||||
|
subsInfo.add(attrSubscription2); |
||||
|
|
||||
|
assertEquals(expectedSubs, subsInfo.getSubs()); |
||||
|
|
||||
|
TbEntitySubEvent updatedEvent = subsInfo.remove(attrSubscription1); |
||||
|
expectedSubs.remove(attrSubscription1); |
||||
|
assertNotNull(updatedEvent); |
||||
|
assertEquals(expectedSubs, subsInfo.getSubs()); |
||||
|
checkEvent(updatedEvent, expectedSubs, ComponentLifecycleEvent.UPDATED); |
||||
|
|
||||
|
TbEntitySubEvent deletedEvent = subsInfo.remove(attrSubscription2); |
||||
|
expectedSubs.remove(attrSubscription2); |
||||
|
assertNotNull(deletedEvent); |
||||
|
assertEquals(expectedSubs, subsInfo.getSubs()); |
||||
|
checkEvent(deletedEvent, expectedSubs, ComponentLifecycleEvent.DELETED); |
||||
|
|
||||
|
assertTrue(subsInfo.isEmpty()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void removeAllTest() { |
||||
|
TbEntityLocalSubsInfo subsInfo = createSubsInfo(); |
||||
|
TenantId tenantId = subsInfo.getTenantId(); |
||||
|
EntityId entityId = subsInfo.getEntityId(); |
||||
|
TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session1") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key1", 1L, "key2", 2L)) |
||||
|
.build(); |
||||
|
|
||||
|
TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session2") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key3", 3L, "key4", 4L)) |
||||
|
.build(); |
||||
|
|
||||
|
TbAttributeSubscription attrSubscription3 = TbAttributeSubscription.builder() |
||||
|
.sessionId("session3") |
||||
|
.tenantId(tenantId) |
||||
|
.entityId(entityId) |
||||
|
.keyStates(Map.of("key5", 5L, "key6", 6L)) |
||||
|
.build(); |
||||
|
|
||||
|
subsInfo.add(attrSubscription1); |
||||
|
subsInfo.add(attrSubscription2); |
||||
|
subsInfo.add(attrSubscription3); |
||||
|
|
||||
|
assertFalse(subsInfo.isEmpty()); |
||||
|
|
||||
|
TbEntitySubEvent updatedEvent = subsInfo.removeAll(List.of(attrSubscription1, attrSubscription2)); |
||||
|
assertNotNull(updatedEvent); |
||||
|
checkEvent(updatedEvent, Set.of(attrSubscription3), ComponentLifecycleEvent.UPDATED); |
||||
|
|
||||
|
assertFalse(subsInfo.isEmpty()); |
||||
|
|
||||
|
TbEntitySubEvent deletedEvent = subsInfo.removeAll(List.of(attrSubscription3)); |
||||
|
assertNotNull(deletedEvent); |
||||
|
checkEvent(deletedEvent, null, ComponentLifecycleEvent.DELETED); |
||||
|
|
||||
|
assertTrue(subsInfo.isEmpty()); |
||||
|
} |
||||
|
|
||||
|
private TbEntityLocalSubsInfo createSubsInfo() { |
||||
|
return new TbEntityLocalSubsInfo(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID())); |
||||
|
} |
||||
|
|
||||
|
private void checkEvent(TbEntitySubEvent event, Set<TbAttributeSubscription> expectedSubs, ComponentLifecycleEvent expectedType) { |
||||
|
assertEquals(expectedType, event.getType()); |
||||
|
TbSubscriptionsInfo info = event.getInfo(); |
||||
|
if (event.getType() == ComponentLifecycleEvent.DELETED) { |
||||
|
assertNull(info); |
||||
|
return; |
||||
|
} |
||||
|
assertNotNull(info); |
||||
|
assertFalse(info.notifications); |
||||
|
assertFalse(info.alarms); |
||||
|
assertFalse(info.attrAllKeys); |
||||
|
assertFalse(info.tsAllKeys); |
||||
|
assertNull(info.tsKeys); |
||||
|
assertEquals(getAttrKeys(expectedSubs), info.attrKeys); |
||||
|
} |
||||
|
|
||||
|
private Set<String> getAttrKeys(Set<TbAttributeSubscription> attributeSubscriptions) { |
||||
|
return attributeSubscriptions.stream().map(s -> s.getKeyStates().keySet()).flatMap(Collection::stream).collect(Collectors.toSet()); |
||||
|
} |
||||
|
} |
||||
@ -1,732 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2024 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.transport.lwm2m.client; |
|
||||
|
|
||||
import org.eclipse.leshan.client.LwM2mClient; |
|
||||
import org.eclipse.leshan.client.resource.BaseObjectEnabler; |
|
||||
import org.eclipse.leshan.client.resource.DummyInstanceEnabler; |
|
||||
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; |
|
||||
import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory; |
|
||||
import org.eclipse.leshan.client.resource.listener.ResourceListener; |
|
||||
import org.eclipse.leshan.client.servers.LwM2mServer; |
|
||||
import org.eclipse.leshan.client.servers.ServersInfoExtractor; |
|
||||
import org.eclipse.leshan.client.util.LinkFormatHelper; |
|
||||
import org.eclipse.leshan.core.Destroyable; |
|
||||
import org.eclipse.leshan.core.LwM2mId; |
|
||||
import org.eclipse.leshan.core.Startable; |
|
||||
import org.eclipse.leshan.core.Stoppable; |
|
||||
import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; |
|
||||
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; |
|
||||
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; |
|
||||
import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; |
|
||||
import org.eclipse.leshan.core.model.ObjectModel; |
|
||||
import org.eclipse.leshan.core.model.ResourceModel; |
|
||||
import org.eclipse.leshan.core.node.LwM2mMultipleResource; |
|
||||
import org.eclipse.leshan.core.node.LwM2mObject; |
|
||||
import org.eclipse.leshan.core.node.LwM2mObjectInstance; |
|
||||
import org.eclipse.leshan.core.node.LwM2mPath; |
|
||||
import org.eclipse.leshan.core.node.LwM2mResource; |
|
||||
import org.eclipse.leshan.core.node.LwM2mResourceInstance; |
|
||||
import org.eclipse.leshan.core.request.BootstrapDeleteRequest; |
|
||||
import org.eclipse.leshan.core.request.BootstrapReadRequest; |
|
||||
import org.eclipse.leshan.core.request.BootstrapWriteRequest; |
|
||||
import org.eclipse.leshan.core.request.ContentFormat; |
|
||||
import org.eclipse.leshan.core.request.CreateRequest; |
|
||||
import org.eclipse.leshan.core.request.DeleteRequest; |
|
||||
import org.eclipse.leshan.core.request.DiscoverRequest; |
|
||||
import org.eclipse.leshan.core.request.DownlinkRequest; |
|
||||
import org.eclipse.leshan.core.request.ExecuteRequest; |
|
||||
import org.eclipse.leshan.core.request.ObserveRequest; |
|
||||
import org.eclipse.leshan.core.request.ReadRequest; |
|
||||
import org.eclipse.leshan.core.request.WriteAttributesRequest; |
|
||||
import org.eclipse.leshan.core.request.WriteRequest; |
|
||||
import org.eclipse.leshan.core.request.WriteRequest.Mode; |
|
||||
import org.eclipse.leshan.core.response.BootstrapDeleteResponse; |
|
||||
import org.eclipse.leshan.core.response.BootstrapReadResponse; |
|
||||
import org.eclipse.leshan.core.response.BootstrapWriteResponse; |
|
||||
import org.eclipse.leshan.core.response.CreateResponse; |
|
||||
import org.eclipse.leshan.core.response.DeleteResponse; |
|
||||
import org.eclipse.leshan.core.response.DiscoverResponse; |
|
||||
import org.eclipse.leshan.core.response.ExecuteResponse; |
|
||||
import org.eclipse.leshan.core.response.ObserveResponse; |
|
||||
import org.eclipse.leshan.core.response.ReadResponse; |
|
||||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|
||||
import org.eclipse.leshan.core.response.WriteResponse; |
|
||||
import org.slf4j.Logger; |
|
||||
import org.slf4j.LoggerFactory; |
|
||||
|
|
||||
import java.util.ArrayList; |
|
||||
import java.util.Arrays; |
|
||||
import java.util.Collection; |
|
||||
import java.util.Collections; |
|
||||
import java.util.HashMap; |
|
||||
import java.util.List; |
|
||||
import java.util.Map; |
|
||||
import java.util.Map.Entry; |
|
||||
|
|
||||
public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyable, Startable, Stoppable { |
|
||||
|
|
||||
private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class); |
|
||||
|
|
||||
protected Map<Integer, LwM2mInstanceEnabler> instances; |
|
||||
|
|
||||
protected LwM2mInstanceEnablerFactory instanceFactory; |
|
||||
protected ContentFormat defaultContentFormat; |
|
||||
|
|
||||
private LinkFormatHelper tbLinkFormatHelper; |
|
||||
protected Map<LwM2mPath, LwM2mAttributeSet> lwM2mAttributes; |
|
||||
public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map<Integer, LwM2mInstanceEnabler> instances, |
|
||||
LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { |
|
||||
super(id, objectModel); |
|
||||
this.instances = new HashMap<>(instances); |
|
||||
; |
|
||||
this.instanceFactory = instanceFactory; |
|
||||
this.defaultContentFormat = defaultContentFormat; |
|
||||
for (Entry<Integer, LwM2mInstanceEnabler> entry : this.instances.entrySet()) { |
|
||||
instances.put(entry.getKey(), entry.getValue()); |
|
||||
listenInstance(entry.getValue(), entry.getKey()); |
|
||||
} |
|
||||
this.lwM2mAttributes = new HashMap<>(); |
|
||||
} |
|
||||
|
|
||||
public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) { |
|
||||
super(id, objectModel); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public synchronized List<Integer> getAvailableInstanceIds() { |
|
||||
List<Integer> ids = new ArrayList<>(instances.keySet()); |
|
||||
Collections.sort(ids); |
|
||||
return ids; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public synchronized List<Integer> getAvailableResourceIds(int instanceId) { |
|
||||
LwM2mInstanceEnabler instanceEnabler = instances.get(instanceId); |
|
||||
if (instanceEnabler != null) { |
|
||||
return instanceEnabler.getAvailableResourceIds(getObjectModel()); |
|
||||
} else { |
|
||||
return Collections.emptyList(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) { |
|
||||
instances.put(instanceId, newInstance); |
|
||||
listenInstance(newInstance, instanceId); |
|
||||
fireInstancesAdded(instanceId); |
|
||||
} |
|
||||
|
|
||||
public synchronized LwM2mInstanceEnabler getInstance(int instanceId) { |
|
||||
return instances.get(instanceId); |
|
||||
} |
|
||||
|
|
||||
public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) { |
|
||||
LwM2mInstanceEnabler removedInstance = instances.remove(instanceId); |
|
||||
if (removedInstance != null) { |
|
||||
fireInstancesRemoved(removedInstance.getId()); |
|
||||
} |
|
||||
return removedInstance; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected CreateResponse doCreate(LwM2mServer server, CreateRequest request) { |
|
||||
if (!getObjectModel().multiple && instances.size() > 0) { |
|
||||
return CreateResponse.badRequest("an instance already exist for this single instance object"); |
|
||||
} |
|
||||
|
|
||||
if (request.unknownObjectInstanceId()) { |
|
||||
// create instance
|
|
||||
LwM2mInstanceEnabler newInstance = createInstance(server, getObjectModel().multiple ? null : 0, |
|
||||
request.getResources()); |
|
||||
|
|
||||
// add new instance to this object
|
|
||||
instances.put(newInstance.getId(), newInstance); |
|
||||
listenInstance(newInstance, newInstance.getId()); |
|
||||
fireInstancesAdded(newInstance.getId()); |
|
||||
|
|
||||
return CreateResponse |
|
||||
.success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString()); |
|
||||
} else { |
|
||||
List<LwM2mObjectInstance> instanceNodes = request.getObjectInstances(); |
|
||||
|
|
||||
// checks single object instances
|
|
||||
if (!getObjectModel().multiple) { |
|
||||
if (request.getObjectInstances().size() > 1) { |
|
||||
return CreateResponse.badRequest("can not create several instances on this single instance object"); |
|
||||
} |
|
||||
if (request.getObjectInstances().get(0).getId() != 0) { |
|
||||
return CreateResponse.badRequest("single instance object must use 0 as ID"); |
|
||||
} |
|
||||
} |
|
||||
// ensure instance does not already exists
|
|
||||
for (LwM2mObjectInstance instance : instanceNodes) { |
|
||||
if (instances.containsKey(instance.getId())) { |
|
||||
return CreateResponse.badRequest(String.format("instance %d already exists", instance.getId())); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// create the new instances
|
|
||||
int[] instanceIds = new int[request.getObjectInstances().size()]; |
|
||||
int i = 0; |
|
||||
for (LwM2mObjectInstance instance : request.getObjectInstances()) { |
|
||||
// create instance
|
|
||||
LwM2mInstanceEnabler newInstance = createInstance(server, instance.getId(), |
|
||||
instance.getResources().values()); |
|
||||
|
|
||||
// add new instance to this object
|
|
||||
instances.put(newInstance.getId(), newInstance); |
|
||||
listenInstance(newInstance, newInstance.getId()); |
|
||||
|
|
||||
// store instance ids
|
|
||||
instanceIds[i] = newInstance.getId(); |
|
||||
i++; |
|
||||
} |
|
||||
fireInstancesAdded(instanceIds); |
|
||||
return CreateResponse.success(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected LwM2mInstanceEnabler createInstance(LwM2mServer server, Integer instanceId, |
|
||||
Collection<LwM2mResource> resources) { |
|
||||
// create the new instance
|
|
||||
LwM2mInstanceEnabler newInstance = instanceFactory.create(getObjectModel(), instanceId, instances.keySet()); |
|
||||
newInstance.setLwM2mClient(getLwm2mClient()); |
|
||||
|
|
||||
// add/write resource
|
|
||||
for (LwM2mResource resource : resources) { |
|
||||
newInstance.write(server, true, resource.getId(), resource); |
|
||||
} |
|
||||
|
|
||||
return newInstance; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected ReadResponse doRead(LwM2mServer server, ReadRequest request) { |
|
||||
LwM2mPath path = request.getPath(); |
|
||||
|
|
||||
// Manage Object case
|
|
||||
if (path.isObject()) { |
|
||||
List<LwM2mObjectInstance> lwM2mObjectInstances = new ArrayList<>(); |
|
||||
for (LwM2mInstanceEnabler instance : instances.values()) { |
|
||||
ReadResponse response = instance.read(server); |
|
||||
if (response.isSuccess()) { |
|
||||
lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); |
|
||||
} |
|
||||
} |
|
||||
return ReadResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); |
|
||||
} |
|
||||
|
|
||||
// Manage Instance case
|
|
||||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|
||||
if (instance == null) |
|
||||
return ReadResponse.notFound(); |
|
||||
|
|
||||
if (path.getResourceId() == null) { |
|
||||
return instance.read(server); |
|
||||
} |
|
||||
|
|
||||
// Manage Resource case
|
|
||||
if (path.getResourceInstanceId() == null) { |
|
||||
return instance.read(server, path.getResourceId()); |
|
||||
} |
|
||||
|
|
||||
// Manage Resource Instance case
|
|
||||
return instance.read(server, path.getResourceId(), path.getResourceInstanceId()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected BootstrapReadResponse doRead(LwM2mServer server, BootstrapReadRequest request) { |
|
||||
// Basic implementation we delegate to classic Read Request
|
|
||||
ReadResponse response = doRead(server, |
|
||||
new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest())); |
|
||||
return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected ObserveResponse doObserve(final LwM2mServer server, final ObserveRequest request) { |
|
||||
final LwM2mPath path = request.getPath(); |
|
||||
|
|
||||
// Manage Object case
|
|
||||
if (path.isObject()) { |
|
||||
List<LwM2mObjectInstance> lwM2mObjectInstances = new ArrayList<>(); |
|
||||
for (LwM2mInstanceEnabler instance : instances.values()) { |
|
||||
ReadResponse response = instance.observe(server); |
|
||||
if (response.isSuccess()) { |
|
||||
lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); |
|
||||
} |
|
||||
} |
|
||||
return ObserveResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); |
|
||||
} |
|
||||
|
|
||||
// Manage Instance case
|
|
||||
final LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|
||||
if (instance == null) |
|
||||
return ObserveResponse.notFound(); |
|
||||
|
|
||||
if (path.getResourceId() == null) { |
|
||||
return instance.observe(server); |
|
||||
} |
|
||||
|
|
||||
// Manage Resource case
|
|
||||
if (path.getResourceInstanceId() == null) { |
|
||||
return instance.observe(server, path.getResourceId()); |
|
||||
} |
|
||||
|
|
||||
// Manage Resource Instance case
|
|
||||
return instance.observe(server, path.getResourceId(), path.getResourceInstanceId()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected WriteResponse doWrite(LwM2mServer server, WriteRequest request) { |
|
||||
LwM2mPath path = request.getPath(); |
|
||||
|
|
||||
// Manage Instance case
|
|
||||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|
||||
if (instance == null) |
|
||||
return WriteResponse.notFound(); |
|
||||
|
|
||||
if (path.isObjectInstance()) { |
|
||||
return instance.write(server, request.isReplaceRequest(), (LwM2mObjectInstance) request.getNode()); |
|
||||
} |
|
||||
|
|
||||
// Manage Resource case
|
|
||||
if (path.getResourceInstanceId() == null) { |
|
||||
return instance.write(server, request.isReplaceRequest(), path.getResourceId(), |
|
||||
(LwM2mResource) request.getNode()); |
|
||||
} |
|
||||
|
|
||||
// Manage Resource Instance case
|
|
||||
return instance.write(server, false, path.getResourceId(), path.getResourceInstanceId(), |
|
||||
((LwM2mResourceInstance) request.getNode())); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected BootstrapWriteResponse doWrite(LwM2mServer server, BootstrapWriteRequest request) { |
|
||||
LwM2mPath path = request.getPath(); |
|
||||
|
|
||||
// Manage Object case
|
|
||||
if (path.isObject()) { |
|
||||
for (LwM2mObjectInstance instanceNode : ((LwM2mObject) request.getNode()).getInstances().values()) { |
|
||||
LwM2mInstanceEnabler instanceEnabler = instances.get(instanceNode.getId()); |
|
||||
if (instanceEnabler == null) { |
|
||||
doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); |
|
||||
} else { |
|
||||
doWrite(server, new WriteRequest(Mode.REPLACE, path.getObjectId(), instanceEnabler.getId(), |
|
||||
instanceNode.getResources().values())); |
|
||||
} |
|
||||
} |
|
||||
return BootstrapWriteResponse.success(); |
|
||||
} |
|
||||
|
|
||||
// Manage Instance case
|
|
||||
if (path.isObjectInstance()) { |
|
||||
LwM2mObjectInstance instanceNode = (LwM2mObjectInstance) request.getNode(); |
|
||||
LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); |
|
||||
if (instanceEnabler == null) { |
|
||||
doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); |
|
||||
} else { |
|
||||
doWrite(server, new WriteRequest(Mode.REPLACE, request.getContentFormat(), path.getObjectId(), |
|
||||
path.getObjectInstanceId(), instanceNode.getResources().values())); |
|
||||
} |
|
||||
return BootstrapWriteResponse.success(); |
|
||||
} |
|
||||
|
|
||||
// Manage resource case
|
|
||||
LwM2mResource resource = (LwM2mResource) request.getNode(); |
|
||||
LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); |
|
||||
if (instanceEnabler == null) { |
|
||||
doCreate(server, new CreateRequest(path.getObjectId(), |
|
||||
new LwM2mObjectInstance(path.getObjectInstanceId(), resource))); |
|
||||
} else { |
|
||||
instanceEnabler.write(server, true, path.getResourceId(), resource); |
|
||||
} |
|
||||
return BootstrapWriteResponse.success(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected ExecuteResponse doExecute(LwM2mServer server, ExecuteRequest request) { |
|
||||
LwM2mPath path = request.getPath(); |
|
||||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|
||||
if (instance == null) { |
|
||||
return ExecuteResponse.notFound(); |
|
||||
} |
|
||||
return instance.execute(server, path.getResourceId(), request.getArguments()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected DeleteResponse doDelete(LwM2mServer server, DeleteRequest request) { |
|
||||
LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId()); |
|
||||
if (deletedInstance != null) { |
|
||||
deletedInstance.onDelete(server); |
|
||||
fireInstancesRemoved(deletedInstance.getId()); |
|
||||
return DeleteResponse.success(); |
|
||||
} |
|
||||
return DeleteResponse.notFound(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public BootstrapDeleteResponse doDelete(LwM2mServer server, BootstrapDeleteRequest request) { |
|
||||
if (request.getPath().isRoot() || request.getPath().isObject()) { |
|
||||
if (id == LwM2mId.SECURITY) { |
|
||||
// For security object, we clean everything except bootstrap Server account.
|
|
||||
|
|
||||
// Get bootstrap account and store removed instances ids
|
|
||||
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerAccount = null; |
|
||||
int[] instanceIds = new int[instances.size()]; |
|
||||
int i = 0; |
|
||||
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) { |
|
||||
if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) { |
|
||||
bootstrapServerAccount = instance; |
|
||||
} else { |
|
||||
// Store instance ids
|
|
||||
instanceIds[i] = instance.getKey(); |
|
||||
i++; |
|
||||
} |
|
||||
} |
|
||||
// Clear everything
|
|
||||
instances.clear(); |
|
||||
|
|
||||
// Put bootstrap account again
|
|
||||
if (bootstrapServerAccount != null) { |
|
||||
instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue()); |
|
||||
} |
|
||||
|
|
||||
fireInstancesRemoved(instanceIds); |
|
||||
return BootstrapDeleteResponse.success(); |
|
||||
} else if (id == LwM2mId.OSCORE) { |
|
||||
// For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account.
|
|
||||
|
|
||||
// Get bootstrap account
|
|
||||
LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( |
|
||||
getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); |
|
||||
// Get OSCORE instance ID associated to it
|
|
||||
Integer bootstrapOscoreInstanceId = bootstrapInstance != null |
|
||||
? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) |
|
||||
: null; |
|
||||
|
|
||||
// if bootstrap server use OSCORE,
|
|
||||
// search the OSCORE instance for this ID and store removed instances ids
|
|
||||
if (bootstrapOscoreInstanceId != null) { |
|
||||
Entry<Integer, LwM2mInstanceEnabler> bootstrapServerOscore = null; |
|
||||
int[] instanceIds = new int[instances.size()]; |
|
||||
int i = 0; |
|
||||
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) { |
|
||||
if (bootstrapOscoreInstanceId.equals(instance.getKey())) { |
|
||||
bootstrapServerOscore = instance; |
|
||||
} else { |
|
||||
// Store instance ids
|
|
||||
instanceIds[i] = instance.getKey(); |
|
||||
i++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Clear everything
|
|
||||
instances.clear(); |
|
||||
|
|
||||
// Put bootstrap OSCORE instance again
|
|
||||
if (bootstrapServerOscore != null) { |
|
||||
instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue()); |
|
||||
} |
|
||||
fireInstancesRemoved(instanceIds); |
|
||||
return BootstrapDeleteResponse.success(); |
|
||||
} |
|
||||
// else delete everything.
|
|
||||
} |
|
||||
|
|
||||
// In all other cases, just delete everything
|
|
||||
instances.clear(); |
|
||||
// fired instances removed
|
|
||||
int[] instanceIds = new int[instances.size()]; |
|
||||
int i = 0; |
|
||||
for (Entry<Integer, LwM2mInstanceEnabler> instance : instances.entrySet()) { |
|
||||
instanceIds[i] = instance.getKey(); |
|
||||
i++; |
|
||||
} |
|
||||
fireInstancesRemoved(instanceIds); |
|
||||
|
|
||||
return BootstrapDeleteResponse.success(); |
|
||||
} else if (request.getPath().isObjectInstance()) { |
|
||||
if (id == LwM2mId.SECURITY) { |
|
||||
// For security object, deleting bootstrap Server account is not allowed
|
|
||||
LwM2mInstanceEnabler instance = instances.get(request.getPath().getObjectInstanceId()); |
|
||||
if (instance == null) { |
|
||||
return BootstrapDeleteResponse |
|
||||
.badRequest(String.format("Instance %s not found", request.getPath())); |
|
||||
} else if (ServersInfoExtractor.isBootstrapServer(instance)) { |
|
||||
return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted"); |
|
||||
} |
|
||||
} else if (id == LwM2mId.OSCORE) { |
|
||||
// For OSCORE object, deleting instance linked to Bootstrap account is not allowed
|
|
||||
|
|
||||
// Get bootstrap instance
|
|
||||
LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( |
|
||||
getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); |
|
||||
// Get OSCORE instance ID associated to it
|
|
||||
Integer bootstrapOscoreInstanceId = bootstrapInstance != null |
|
||||
? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) |
|
||||
: null; |
|
||||
|
|
||||
if (bootstrapOscoreInstanceId != null |
|
||||
&& bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) { |
|
||||
return BootstrapDeleteResponse |
|
||||
.badRequest("OSCORE instance linked to bootstrap server can not be deleted"); |
|
||||
} |
|
||||
} |
|
||||
if (null != instances.remove(request.getPath().getObjectInstanceId())) { |
|
||||
fireInstancesRemoved(request.getPath().getObjectInstanceId()); |
|
||||
return BootstrapDeleteResponse.success(); |
|
||||
} else { |
|
||||
return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath())); |
|
||||
} |
|
||||
} |
|
||||
return BootstrapDeleteResponse.badRequest(String.format("unexcepted path %s", request.getPath())); |
|
||||
} |
|
||||
|
|
||||
protected void listenInstance(LwM2mInstanceEnabler instance, final int instanceId) { |
|
||||
instance.addResourceListener(new ResourceListener() { |
|
||||
@Override |
|
||||
public void resourceChanged(LwM2mPath... paths) { |
|
||||
for (LwM2mPath path : paths) { |
|
||||
if (!isValid(instanceId, path)) { |
|
||||
LOG.warn("InstanceEnabler ({}) of object ({}) try to raise a change of {} which seems invalid.", |
|
||||
instanceId, getId(), path); |
|
||||
} |
|
||||
} |
|
||||
fireResourcesChanged(paths); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
protected boolean isValid(int instanceId, LwM2mPath pathToValidate) { |
|
||||
if (!(pathToValidate.isResource() || pathToValidate.isResourceInstance())) |
|
||||
return false; |
|
||||
|
|
||||
if (pathToValidate.getObjectId() != getId()) { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (pathToValidate.getObjectInstanceId() != instanceId) { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ContentFormat getDefaultEncodingFormat(DownlinkRequest<?> request) { |
|
||||
return defaultContentFormat; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void init(LwM2mClient client, LinkFormatHelper linkFormatHelper) { |
|
||||
super.init(client, linkFormatHelper); |
|
||||
this.tbLinkFormatHelper = linkFormatHelper; |
|
||||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|
||||
instanceEnabler.setLwM2mClient(client); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void destroy() { |
|
||||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|
||||
if (instanceEnabler instanceof Destroyable) { |
|
||||
((Destroyable) instanceEnabler).destroy(); |
|
||||
} else if (instanceEnabler instanceof Stoppable) { |
|
||||
((Stoppable) instanceEnabler).stop(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void start() { |
|
||||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|
||||
if (instanceEnabler instanceof Startable) { |
|
||||
((Startable) instanceEnabler).start(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void stop() { |
|
||||
for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { |
|
||||
if (instanceEnabler instanceof Stoppable) { |
|
||||
((Stoppable) instanceEnabler).stop(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public synchronized WriteAttributesResponse writeAttributes(LwM2mServer server, WriteAttributesRequest request) { |
|
||||
// execute is not supported for bootstrap
|
|
||||
if (server.isLwm2mBootstrapServer()) { |
|
||||
return WriteAttributesResponse.methodNotAllowed(); |
|
||||
} |
|
||||
// return WriteAttributesResponse.internalServerError("not implemented");
|
|
||||
return doWriteAttributes(server, request); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* <NOTIFICATION> Class Attributes |
|
||||
* - pmin (def = 0(sec)) Integer Resource/Object Instance/Object Readable Resource |
|
||||
* - pmax (def = -- ) Integer Resource/Object Instance/Object Readable Resource |
|
||||
* - Greater Than gt (def = -- ) Float Resource Numerical&Readable Resource |
|
||||
* - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource |
|
||||
* - Step st (def = -- ) Float Resource Numerical&Readable Resource |
|
||||
*/ |
|
||||
public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { |
|
||||
LwM2mPath lwM2mPath = request.getPath(); |
|
||||
LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath); |
|
||||
Map <String, LwM2mAttribute<?>> attributes = new HashMap<>(); |
|
||||
|
|
||||
for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) { |
|
||||
if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) { |
|
||||
if (lwM2mPath.isObject() || lwM2mPath.isObjectInstance() || lwM2mPath.isResource()) { |
|
||||
attributes.put(attr.getName(), attr); |
|
||||
} else { |
|
||||
return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource/Object Instance/Object."); |
|
||||
} |
|
||||
} else if (attr.getName().equals("gt") || attr.getName().equals("lt") || attr.getName().equals("st")) { |
|
||||
if (lwM2mPath.isResource()) { |
|
||||
attributes.put(attr.getName(), attr); |
|
||||
} else { |
|
||||
return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
if (attributes.size()>0){ |
|
||||
if (attributeSet == null) { |
|
||||
attributeSet = new LwM2mAttributeSet(attributes.values()); |
|
||||
} else { |
|
||||
Iterable<LwM2mAttribute<?>> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes(); |
|
||||
Map <String, LwM2mAttribute<?>> attributesOld = new HashMap<>(); |
|
||||
for (LwM2mAttribute<?> attr : lwM2mAttributeIterable) { |
|
||||
attributesOld.put(attr.getName(), attr); |
|
||||
} |
|
||||
attributesOld.putAll(attributes); |
|
||||
attributeSet = new LwM2mAttributeSet(attributesOld.values()); |
|
||||
} |
|
||||
lwM2mAttributes.put(lwM2mPath, attributeSet); |
|
||||
return WriteAttributesResponse.success(); |
|
||||
} |
|
||||
return WriteAttributesResponse.internalServerError("not implemented"); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) { |
|
||||
|
|
||||
if (server.isLwm2mBootstrapServer()) { |
|
||||
// discover is not supported for bootstrap
|
|
||||
return DiscoverResponse.methodNotAllowed(); |
|
||||
} |
|
||||
|
|
||||
if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { |
|
||||
return DiscoverResponse.notFound(); |
|
||||
} |
|
||||
return doDiscover(server, request); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
protected DiscoverResponse doDiscover(LwM2mServer server, DiscoverRequest request) { |
|
||||
|
|
||||
LwM2mPath path = request.getPath(); |
|
||||
if (path.isObject()) { |
|
||||
LwM2mLink[] ObjectLinks = linkUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(this, null), server); |
|
||||
return DiscoverResponse.success(ObjectLinks); |
|
||||
|
|
||||
} else if (path.isObjectInstance()) { |
|
||||
// Manage discover on instance
|
|
||||
if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) |
|
||||
return DiscoverResponse.notFound(); |
|
||||
|
|
||||
LwM2mLink[] instanceLink = linkUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null), server); |
|
||||
return DiscoverResponse.success(instanceLink); |
|
||||
|
|
||||
} else if (path.isResource()) { |
|
||||
// Manage discover on resource
|
|
||||
if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) |
|
||||
return DiscoverResponse.notFound(); |
|
||||
|
|
||||
ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); |
|
||||
if (resourceModel == null) |
|
||||
return DiscoverResponse.notFound(); |
|
||||
|
|
||||
if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) |
|
||||
return DiscoverResponse.notFound(); |
|
||||
|
|
||||
LwM2mLink resourceLink = linkAddAttribute( |
|
||||
this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null), |
|
||||
server); |
|
||||
return DiscoverResponse.success(new LwM2mLink[] { resourceLink }); |
|
||||
} |
|
||||
return DiscoverResponse.badRequest(null); |
|
||||
} |
|
||||
|
|
||||
private LwM2mLink[] linkUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { |
|
||||
return Arrays.stream(links) |
|
||||
.map(link -> linkAddAttribute(link, server)) |
|
||||
.toArray(LwM2mLink[]::new); |
|
||||
} |
|
||||
|
|
||||
private LwM2mLink linkAddAttribute(LwM2mLink link, LwM2mServer server) { |
|
||||
|
|
||||
LwM2mAttributeSet lwM2mAttributeSetDop = null; |
|
||||
if (this.lwM2mAttributes.get(link.getPath())!= null){ |
|
||||
lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); |
|
||||
} |
|
||||
LwM2mAttribute resourceAttributeDim = getResourceAttributes (server, link.getPath()); |
|
||||
|
|
||||
Map <String, LwM2mAttribute<?>> attributes = new HashMap<>(); |
|
||||
if (link.getAttributes() != null) { |
|
||||
for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { |
|
||||
attributes.put(attr.getName(), attr); |
|
||||
} |
|
||||
} |
|
||||
if (lwM2mAttributeSetDop != null) { |
|
||||
for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { |
|
||||
attributes.put(attr.getName(), attr); |
|
||||
} |
|
||||
} |
|
||||
if (resourceAttributeDim != null) { |
|
||||
attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); |
|
||||
} |
|
||||
return new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values()); |
|
||||
} |
|
||||
|
|
||||
protected LwM2mAttribute getResourceAttributes (LwM2mServer server, LwM2mPath path) { |
|
||||
ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); |
|
||||
if (path.isResource() && resourceModel.multiple) { |
|
||||
return getResourceAttributeDim(path, server); |
|
||||
} |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) { |
|
||||
LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); |
|
||||
try { |
|
||||
ReadResponse readResponse = instance.read(server, path.getResourceId()); |
|
||||
if (readResponse.getCode().getCode()==205 && readResponse.getContent() instanceof LwM2mMultipleResource) { |
|
||||
long valueDim = ((LwM2mMultipleResource)readResponse.getContent()).getInstances().size(); |
|
||||
return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim); |
|
||||
} else { |
|
||||
return null; |
|
||||
} |
|
||||
} catch (Exception e ){ |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
@ -1,71 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2024 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.transport.lwm2m.client; |
|
||||
|
|
||||
import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory; |
|
||||
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; |
|
||||
import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; |
|
||||
import org.eclipse.leshan.client.resource.ObjectsInitializer; |
|
||||
import org.eclipse.leshan.core.model.LwM2mModel; |
|
||||
import org.eclipse.leshan.core.model.ObjectModel; |
|
||||
|
|
||||
import java.util.ArrayList; |
|
||||
import java.util.HashMap; |
|
||||
import java.util.List; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
public class TbObjectsInitializer extends ObjectsInitializer { |
|
||||
|
|
||||
|
|
||||
public TbObjectsInitializer(LwM2mModel model) { |
|
||||
super(model); |
|
||||
} |
|
||||
|
|
||||
public List<LwM2mObjectEnabler> create(int... objectId) { |
|
||||
List<LwM2mObjectEnabler> enablers = new ArrayList<>(); |
|
||||
for (int anObjectId : objectId) { |
|
||||
LwM2mObjectEnabler objectEnabler = create(anObjectId); |
|
||||
if (objectEnabler != null) |
|
||||
enablers.add(objectEnabler); |
|
||||
} |
|
||||
return enablers; |
|
||||
} |
|
||||
|
|
||||
public LwM2mObjectEnabler create(int objectId) { |
|
||||
ObjectModel objectModel = model.getObjectModel(objectId); |
|
||||
if (objectModel == null) { |
|
||||
throw new IllegalArgumentException( |
|
||||
"Cannot create object for id " + objectId + " because no model is defined for this id."); |
|
||||
} |
|
||||
return createNodeEnabler(objectModel); |
|
||||
} |
|
||||
|
|
||||
protected LwM2mObjectEnabler createNodeEnabler(ObjectModel objectModel) { |
|
||||
Map<Integer, LwM2mInstanceEnabler> instances = new HashMap<>(); |
|
||||
LwM2mInstanceEnabler[] newInstances = createInstances(objectModel); |
|
||||
for (LwM2mInstanceEnabler instance : newInstances) { |
|
||||
// set id if not already set
|
|
||||
if (instance.getId() == null) { |
|
||||
int id = BaseInstanceEnablerFactory.generateNewInstanceId(instances.keySet()); |
|
||||
instance.setId(id); |
|
||||
} |
|
||||
instance.setModel(objectModel); |
|
||||
instances.put(instance.getId(), instance); |
|
||||
} |
|
||||
return new TbLwm2mObjectEnabler(objectModel.id, objectModel, instances, getFactoryFor(objectModel), |
|
||||
getContentFormat(objectModel.id)); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.transport.lwm2m.ota.sql; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; |
||||
|
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.awaitility.Awaitility.await; |
||||
|
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; |
||||
|
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; |
||||
|
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; |
||||
|
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; |
||||
|
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; |
||||
|
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class Ota9LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { |
||||
|
|
||||
|
/** |
||||
|
* => Start -> INITIAL (State=0) -> DOWNLOAD STARTED; |
||||
|
* => PKG / URI Write -> DOWNLOAD STARTED (Res=1 (Downloading) && State=1) -> DOWNLOADED |
||||
|
* => PKG Written -> DOWNLOADED (Res=1 Initial && State=2) -> DELIVERED; |
||||
|
* => PKG integrity verified -> DELIVERED (Res=3 (Successfully Downloaded and package integrity verified) && State=3) -> INSTALLED; |
||||
|
* => Install -> INSTALLED (Res=2 SW successfully installed) && State=4) -> Start |
||||
|
* |
||||
|
* */ |
||||
|
@Test |
||||
|
public void testSoftwareUpdateByObject9() throws Exception { |
||||
|
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA9, getBootstrapServerCredentialsNoSec(NONE)); |
||||
|
DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_OTA9, transportConfiguration); |
||||
|
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); |
||||
|
final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9, deviceProfile.getId()); |
||||
|
createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9); |
||||
|
awaitObserveReadAll(4, device.getId().getId().toString()); |
||||
|
|
||||
|
device.setSoftwareId(createSoftware(deviceProfile.getId()).getId()); |
||||
|
final Device savedDevice = doPost("/api/device", device, Device.class); //sync call
|
||||
|
|
||||
|
assertThat(savedDevice).as("saved device").isNotNull(); |
||||
|
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); |
||||
|
|
||||
|
expectedStatuses = List.of( |
||||
|
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); |
||||
|
List<TsKvEntry> ts = await("await on timeseries") |
||||
|
.atMost(TIMEOUT, TimeUnit.SECONDS) |
||||
|
.until(() -> getFwSwStateTelemetryFromAPI(device.getId().getId(), "sw_state"), this::predicateForStatuses); |
||||
|
log.warn("Object9: Got the ts: {}", ts); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.transport.lwm2m.rpc.sql; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.node.ArrayNode; |
||||
|
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
||||
|
import java.util.concurrent.atomic.AtomicReference; |
||||
|
import static java.util.concurrent.TimeUnit.SECONDS; |
||||
|
import static org.awaitility.Awaitility.await; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; |
||||
|
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { |
||||
|
|
||||
|
/** |
||||
|
* Read {"id":"/3303/12/5700"} |
||||
|
* Trigger a Send operation from the client with multiple values for the same resource as a payload |
||||
|
* acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] |
||||
|
* 2 values for the resource /3303/12/5700 should be stored with: |
||||
|
* - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 |
||||
|
* - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
@Test |
||||
|
public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { |
||||
|
// init test
|
||||
|
int cntValues = 2; |
||||
|
int resourceId = 5700; |
||||
|
String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; |
||||
|
sendRPCById(expectedIdVer); |
||||
|
|
||||
|
// verify time start/end send CollectedValue;
|
||||
|
await().atMost(40, SECONDS).until(() -> RESOURCE_ID_3303_12_5700_TS_0 > 0 |
||||
|
&& RESOURCE_ID_3303_12_5700_TS_1 > 0); |
||||
|
|
||||
|
// verify result read: verify count value: 1-2: send CollectedValue;
|
||||
|
AtomicReference<ObjectNode> actualValues = new AtomicReference<>(); |
||||
|
await().atMost(40, SECONDS).until(() -> { |
||||
|
actualValues.set(doGetAsync( |
||||
|
"/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" |
||||
|
+ RESOURCE_ID_NAME_3303_12_5700 |
||||
|
+ "&startTs=" + (RESOURCE_ID_3303_12_5700_TS_0 - RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) |
||||
|
+ "&endTs=" + (RESOURCE_ID_3303_12_5700_TS_1 + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) |
||||
|
+ "&interval=0&limit=100&useStrictDataTypes=false", |
||||
|
ObjectNode.class)); |
||||
|
return actualValues.get() != null && actualValues.get().size() > 0 |
||||
|
&& actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() >= cntValues && verifyTs(actualValues); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private boolean verifyTs(AtomicReference<ObjectNode> actualValues) { |
||||
|
String expectedVal_0 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); |
||||
|
String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); |
||||
|
ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); |
||||
|
long actualTS0 = 0; |
||||
|
long actualTS1 = 0; |
||||
|
for (JsonNode tsNode : actual) { |
||||
|
if (tsNode.get("value").asText().equals(expectedVal_0)) { |
||||
|
actualTS0 = tsNode.get("ts").asLong(); |
||||
|
} else if (tsNode.get("value").asText().equals(expectedVal_1)) { |
||||
|
actualTS1 = tsNode.get("ts").asLong(); |
||||
|
} |
||||
|
} |
||||
|
return actualTS0 >= RESOURCE_ID_3303_12_5700_TS_0 |
||||
|
&& actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1 |
||||
|
&& (actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; |
||||
|
} |
||||
|
|
||||
|
private String sendRPCById(String path) throws Exception { |
||||
|
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; |
||||
|
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.transport.coap.efento.utils; |
||||
|
|
||||
|
public enum PulseCounterType { |
||||
|
|
||||
|
WATER_CNT_ACC("water_cnt_acc_", 100), |
||||
|
PULSE_CNT_ACC("pulse_cnt_acc_", 1000), |
||||
|
ELEC_METER_ACC("elec_meter_acc_", 1000), |
||||
|
PULSE_CNT_ACC_WIDE("pulse_cnt_acc_wide_", 1000000); |
||||
|
|
||||
|
private final String prefix; |
||||
|
private final int majorResolution; |
||||
|
|
||||
|
PulseCounterType(String prefix, int majorResolution) { |
||||
|
this.prefix = prefix; |
||||
|
this.majorResolution = majorResolution; |
||||
|
} |
||||
|
|
||||
|
public String getPrefix() { |
||||
|
return prefix; |
||||
|
} |
||||
|
|
||||
|
public int getMajorResolution() { |
||||
|
return majorResolution; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.sqlts; |
||||
|
|
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import org.apache.commons.lang3.RandomStringUtils; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.StringDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.dao.service.AbstractServiceTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
import org.thingsboard.server.dao.timeseries.TimeseriesLatestDao; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.junit.jupiter.api.Assertions.assertEquals; |
||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull; |
||||
|
import static org.junit.jupiter.api.Assertions.assertNull; |
||||
|
import static org.junit.jupiter.api.Assertions.assertTrue; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class SqlTimeseriesLatestDaoTest extends AbstractServiceTest { |
||||
|
|
||||
|
@Autowired |
||||
|
private TimeseriesLatestDao timeseriesLatestDao; |
||||
|
|
||||
|
@Test |
||||
|
public void saveLatestTest() throws Exception { |
||||
|
DeviceId deviceId = new DeviceId(UUID.randomUUID()); |
||||
|
var entry = createEntry("key", 1000); |
||||
|
Long version = timeseriesLatestDao.saveLatest(tenantId, deviceId, entry).get(); |
||||
|
assertNotNull(version); |
||||
|
assertTrue(version > 0); |
||||
|
|
||||
|
TsKvEntry foundEntry = timeseriesLatestDao.findLatest(tenantId, deviceId, "key").get(); |
||||
|
assertNotNull(foundEntry); |
||||
|
equalsIgnoreVersion(entry, foundEntry); |
||||
|
assertEquals(version, foundEntry.getVersion()); |
||||
|
|
||||
|
var updatedEntry = createEntry("key", 2000); |
||||
|
Long updatedVersion = timeseriesLatestDao.saveLatest(tenantId, deviceId, updatedEntry).get(); |
||||
|
assertNotNull(updatedVersion); |
||||
|
assertTrue(updatedVersion > version); |
||||
|
|
||||
|
foundEntry = timeseriesLatestDao.findLatest(tenantId, deviceId, "key").get(); |
||||
|
assertNotNull(foundEntry); |
||||
|
equalsIgnoreVersion(updatedEntry, foundEntry); |
||||
|
assertEquals(updatedVersion, foundEntry.getVersion()); |
||||
|
|
||||
|
var oldEntry = createEntry("key", 1); |
||||
|
Long oldVersion = timeseriesLatestDao.saveLatest(tenantId, deviceId, oldEntry).get(); |
||||
|
assertNull(oldVersion); |
||||
|
|
||||
|
foundEntry = timeseriesLatestDao.findLatest(tenantId, deviceId, "key").get(); |
||||
|
assertNotNull(foundEntry); |
||||
|
equalsIgnoreVersion(updatedEntry, foundEntry); |
||||
|
assertEquals(updatedVersion, foundEntry.getVersion()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void updateWithOldTsTest() throws Exception { |
||||
|
DeviceId deviceId = new DeviceId(UUID.randomUUID()); |
||||
|
int n = 50; |
||||
|
for (int i = 0; i < n; i++) { |
||||
|
timeseriesLatestDao.saveLatest(tenantId, deviceId, createEntry("key_" + i, System.currentTimeMillis())); |
||||
|
} |
||||
|
|
||||
|
List<ListenableFuture<Long>> futures = new ArrayList<>(n); |
||||
|
|
||||
|
for (int i = 0; i < n; i++) { |
||||
|
long ts = i % 2 == 0 ? System.currentTimeMillis() : 1000; |
||||
|
futures.add(timeseriesLatestDao.saveLatest(tenantId, deviceId, createEntry("key_" + i, ts))); |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < futures.size(); i++) { |
||||
|
Long version = futures.get(i).get(); |
||||
|
if (i % 2 == 0) { |
||||
|
assertNotNull(version); |
||||
|
assertTrue(version > 0); |
||||
|
} else { |
||||
|
assertNull(version); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TsKvEntry createEntry(String key, long ts) { |
||||
|
return new BasicTsKvEntry(ts, new StringDataEntry(key, RandomStringUtils.random(10))); |
||||
|
} |
||||
|
|
||||
|
private void equalsIgnoreVersion(TsKvEntry expected, TsKvEntry actual) { |
||||
|
Assert.assertEquals(expected.getKey(), actual.getKey()); |
||||
|
Assert.assertEquals(expected.getValue(), actual.getValue()); |
||||
|
Assert.assertEquals(expected.getTs(), actual.getTs()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
File diff suppressed because one or more lines are too long
@ -1,68 +1,69 @@ |
|||||
{ |
{ |
||||
"root": true, |
"root": true, |
||||
"ignorePatterns": [ |
"ignorePatterns": [ |
||||
"projects/**/*" |
"projects/**/*" |
||||
], |
], |
||||
"overrides": [ |
"overrides": [ |
||||
{ |
{ |
||||
"files": [ |
"files": [ |
||||
"*.ts" |
"*.ts", |
||||
], |
"*.tsx" |
||||
"parserOptions": { |
], |
||||
"project": [ |
"parserOptions": { |
||||
"tsconfig.json", |
"project": [ |
||||
"e2e/tsconfig.json" |
"tsconfig.json" |
||||
], |
], |
||||
"createDefaultProgram": true |
"createDefaultProgram": true |
||||
}, |
}, |
||||
"extends": [ |
"extends": [ |
||||
"plugin:@angular-eslint/ng-cli-compat", |
"plugin:@angular-eslint/recommended", |
||||
"plugin:@angular-eslint/ng-cli-compat--formatting-add-on", |
"plugin:@angular-eslint/template/process-inline-templates" |
||||
"plugin:@angular-eslint/template/process-inline-templates" |
], |
||||
], |
"rules": { |
||||
"rules": { |
"@typescript-eslint/explicit-member-accessibility": [ |
||||
"@typescript-eslint/explicit-member-accessibility": [ |
"off", |
||||
"off", |
{ |
||||
{ |
"accessibility": "explicit" |
||||
"accessibility": "explicit" |
} |
||||
} |
], |
||||
], |
"arrow-parens": [ |
||||
"arrow-parens": [ |
"off", |
||||
"off", |
"always" |
||||
"always" |
], |
||||
], |
"@angular-eslint/component-selector": [ |
||||
"@angular-eslint/component-selector": [ |
"error", |
||||
"error", |
{ |
||||
{ |
"prefix": [ |
||||
"prefix": [ "tb" ] |
"tb" |
||||
} |
] |
||||
], |
} |
||||
"id-blacklist": [ |
], |
||||
"error", |
"id-blacklist": [ |
||||
"any", |
"error", |
||||
"Number", |
"any", |
||||
"String", |
"Number", |
||||
"string", |
"String", |
||||
"Boolean", |
"string", |
||||
"boolean", |
"Boolean", |
||||
"Undefined", |
"boolean", |
||||
"undefined" |
"Undefined", |
||||
], |
"undefined" |
||||
"import/order": "off", |
], |
||||
"@typescript-eslint/member-ordering": "off", |
"import/order": "off", |
||||
"no-underscore-dangle": "off", |
"@typescript-eslint/member-ordering": "off", |
||||
"@typescript-eslint/naming-convention": "off", |
"no-underscore-dangle": "off", |
||||
"jsdoc/newline-after-description": 0 |
"@typescript-eslint/naming-convention": "off", |
||||
} |
"jsdoc/newline-after-description": 0 |
||||
}, |
} |
||||
{ |
}, |
||||
"files": [ |
{ |
||||
"*.html" |
"files": [ |
||||
], |
"*.html" |
||||
"extends": [ |
], |
||||
"plugin:@angular-eslint/template/recommended" |
"extends": [ |
||||
], |
"plugin:@angular-eslint/template/recommended" |
||||
"rules": {} |
], |
||||
} |
"rules": {} |
||||
] |
} |
||||
|
] |
||||
} |
} |
||||
|
|||||
@ -1,43 +0,0 @@ |
|||||
/* |
|
||||
* Copyright © 2016-2024 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. |
|
||||
*/ |
|
||||
// Protractor configuration file, see link for more information
|
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
|
||||
|
|
||||
const { SpecReporter } = require("jasmine-spec-reporter"); |
|
||||
|
|
||||
exports.config = { |
|
||||
allScriptsTimeout: 11000, |
|
||||
specs: [ |
|
||||
"./src/**/*.e2e-spec.ts", |
|
||||
], |
|
||||
capabilities: { |
|
||||
"browserName": "chrome", |
|
||||
}, |
|
||||
directConnect: true, |
|
||||
baseUrl: "http://localhost:4200/", |
|
||||
framework: "jasmine", |
|
||||
jasmineNodeOpts: { |
|
||||
showColors: true, |
|
||||
defaultTimeoutInterval: 30000, |
|
||||
print: function() {}, |
|
||||
}, |
|
||||
onPrepare() { |
|
||||
require("ts-node").register({ |
|
||||
project: require("path").join(__dirname, "./tsconfig.e2e.json"), |
|
||||
}); |
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); |
|
||||
}, |
|
||||
}; |
|
||||
@ -1,39 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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.
|
|
||||
///
|
|
||||
|
|
||||
import { AppPage } from './app.po'; |
|
||||
import { browser, logging } from 'protractor'; |
|
||||
|
|
||||
describe('workspace-project App', () => { |
|
||||
let page: AppPage; |
|
||||
|
|
||||
beforeEach(() => { |
|
||||
page = new AppPage(); |
|
||||
}); |
|
||||
|
|
||||
it('should display welcome message', () => { |
|
||||
page.navigateTo(); |
|
||||
expect(page.getTitleText()).toEqual('Welcome to tb-license-server!'); |
|
||||
}); |
|
||||
|
|
||||
afterEach(async () => { |
|
||||
// Assert that there are no errors emitted from the browser
|
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER); |
|
||||
expect(logs).not.toContain(jasmine.objectContaining({ |
|
||||
level: logging.Level.SEVERE, |
|
||||
} as logging.Entry)); |
|
||||
}); |
|
||||
}); |
|
||||
@ -1,27 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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.
|
|
||||
///
|
|
||||
|
|
||||
import { browser, by, element } from 'protractor'; |
|
||||
|
|
||||
export class AppPage { |
|
||||
navigateTo() { |
|
||||
return browser.get(browser.baseUrl) as Promise<any>; |
|
||||
} |
|
||||
|
|
||||
getTitleText() { |
|
||||
return element(by.css('tb-root h1')).getText() as Promise<string>; |
|
||||
} |
|
||||
} |
|
||||
@ -1,13 +0,0 @@ |
|||||
{ |
|
||||
"extends": "../tsconfig.json", |
|
||||
"compilerOptions": { |
|
||||
"outDir": "../out-tsc/app", |
|
||||
"module": "commonjs", |
|
||||
"target": "es5", |
|
||||
"types": [ |
|
||||
"jasmine", |
|
||||
"jasminewd2", |
|
||||
"node" |
|
||||
] |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,130 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2024 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. |
||||
|
*/ |
||||
|
|
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
const materialIconDir = path.join('.', 'src', 'assets', 'metadata'); |
||||
|
const mdiMetadata = path.join('.', 'node_modules', '@mdi', 'svg', 'meta.json'); |
||||
|
|
||||
|
async function init() { |
||||
|
const iconsBundle = JSON.parse(await fs.promises.readFile(path.join(materialIconDir, 'material-icons.json'))); |
||||
|
|
||||
|
await getMaterialIconMetadataAndUpdated(iconsBundle); |
||||
|
await getMDIMetadataAndUpdated(iconsBundle); |
||||
|
|
||||
|
await fs.promises.writeFile(path.join(materialIconDir, 'material-icons.json'), JSON.stringify(iconsBundle), 'utf8') |
||||
|
} |
||||
|
|
||||
|
async function getMaterialIconMetadataAndUpdated(iconsBundle){ |
||||
|
const iconsResponse = await fetch('https://fonts.google.com/metadata/icons?key=material_symbols&incomplete=true'); |
||||
|
const iconsText = await iconsResponse.text(); |
||||
|
const clearText = iconsText.substring(iconsText.indexOf("\n") + 1); |
||||
|
|
||||
|
const icons = JSON.parse(clearText).icons; |
||||
|
|
||||
|
let prevItem; |
||||
|
const filterIcons = icons.filter((item) => { |
||||
|
if (prevItem?.name !== item.name && !item.unsupported_families.includes('Material Icons')) { |
||||
|
prevItem = item; |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
}); |
||||
|
|
||||
|
filterIcons.forEach((item, index) => { |
||||
|
const findItem = iconsBundle.find((el) => el.name === item.name); |
||||
|
if (!findItem) { |
||||
|
let prevIndexIcon = 0; |
||||
|
if (index === 0) { |
||||
|
prevIndexIcon = 45; |
||||
|
} else { |
||||
|
let iteration = 0; |
||||
|
while (prevIndexIcon < 45) { |
||||
|
iteration++; |
||||
|
const prevIconName = filterIcons[index - iteration].name; |
||||
|
prevIndexIcon = findPreviousIcon(iconsBundle, prevIconName); |
||||
|
} |
||||
|
} |
||||
|
if (prevIndexIcon >= 0) { |
||||
|
iconsBundle.splice(prevIndexIcon + 1, 0, {name:item.name, tags:item.tags}); |
||||
|
} |
||||
|
console.log('Not found icon:', item.name); |
||||
|
console.count('Not found material icon'); |
||||
|
return; |
||||
|
} |
||||
|
if (JSON.stringify(item.tags) !== JSON.stringify(findItem.tags)) { |
||||
|
findItem.tags = item.tags; |
||||
|
console.log('Difference tags in', item.name); |
||||
|
console.count('Difference tags in material icon'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async function getMDIMetadataAndUpdated(iconsBundle){ |
||||
|
const mdiBundle = JSON.parse(await fs.promises.readFile(mdiMetadata)); |
||||
|
|
||||
|
iconsBundle |
||||
|
.filter(item => item.name.startsWith('mdi:')) |
||||
|
.forEach(item => { |
||||
|
const iconName = item.name.substring(item.name.indexOf(":") + 1); |
||||
|
const findItem = mdiBundle.find((el) => el.name === iconName); |
||||
|
if (!findItem) { |
||||
|
console.error('Delete icon:', item.name); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
mdiBundle.forEach((item, index) => { |
||||
|
const iconName = `mdi:${item.name}` |
||||
|
let iconTags = item.tags; |
||||
|
const iconAliases = item.aliases.map(item => item.replaceAll('-', ' ')); |
||||
|
if (!iconTags.length && item.aliases.length) { |
||||
|
iconTags = iconAliases; |
||||
|
} else if (item.aliases.length) { |
||||
|
iconTags = iconTags.concat(iconAliases); |
||||
|
} |
||||
|
iconTags = iconTags.map(item => item.toLowerCase()); |
||||
|
|
||||
|
const findItem = iconsBundle.find((el) => el.name === iconName); |
||||
|
if (!findItem) { |
||||
|
let prevIndexIcon; |
||||
|
if (index === 0) { |
||||
|
prevIndexIcon = iconsBundle.findIndex(item => item.name.startsWith('mdi:')) |
||||
|
} else { |
||||
|
const prevIconName = `mdi:${mdiBundle[index - 1].name}`; |
||||
|
prevIndexIcon = findPreviousIcon(iconsBundle, prevIconName); |
||||
|
} |
||||
|
if (prevIndexIcon >= 0) { |
||||
|
iconsBundle.splice(prevIndexIcon + 1, 0, {name:iconName, tags:iconTags}); |
||||
|
} |
||||
|
console.log('Not found icon:', iconName); |
||||
|
console.count('Not found mdi icon'); |
||||
|
return; |
||||
|
} |
||||
|
if (JSON.stringify(iconTags) !== JSON.stringify(findItem.tags)) { |
||||
|
findItem.tags = iconTags; |
||||
|
console.log('Difference tags in', iconName); |
||||
|
console.count('Difference tags in mdi icon'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function findPreviousIcon(iconsBundle, findName) { |
||||
|
return iconsBundle.findIndex(item => item.name === findName); |
||||
|
} |
||||
|
|
||||
|
init(); |
||||
@ -1,8 +1,8 @@ |
|||||
diff --git a/node_modules/@angular/core/fesm2020/core.mjs b/node_modules/@angular/core/fesm2020/core.mjs
|
diff --git a/node_modules/@angular/core/fesm2022/core.mjs b/node_modules/@angular/core/fesm2022/core.mjs
|
||||
index e9a9b75..17044d9 100755
|
index 0fa881f..b844dfa 100755
|
||||
--- a/node_modules/@angular/core/fesm2020/core.mjs
|
--- a/node_modules/@angular/core/fesm2022/core.mjs
|
||||
+++ b/node_modules/@angular/core/fesm2020/core.mjs
|
+++ b/node_modules/@angular/core/fesm2022/core.mjs
|
||||
@@ -11053,13 +11053,13 @@ function findDirectiveDefMatches(tView, tNode) {
|
@@ -12868,13 +12868,13 @@ function findDirectiveDefMatches(tView, tNode) {
|
||||
if (isNodeMatchingSelectorList(tNode, def.selectors, /* isProjectionMode */ false)) { |
if (isNodeMatchingSelectorList(tNode, def.selectors, /* isProjectionMode */ false)) { |
||||
matches || (matches = []); |
matches || (matches = []); |
||||
if (isComponentDef(def)) { |
if (isComponentDef(def)) { |
||||
File diff suppressed because one or more lines are too long
@ -1,51 +0,0 @@ |
|||||
diff --git a/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs b/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs
|
|
||||
index e3457ea..a069460 100644
|
|
||||
--- a/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs
|
|
||||
+++ b/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs
|
|
||||
@@ -259,9 +259,9 @@ export class MatDatetimepickerClockComponent {
|
|
||||
value = 0; |
|
||||
} |
|
||||
// Don't close the minutes view if an invalid minute is clicked. |
|
||||
- if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|
||||
- return;
|
|
||||
- }
|
|
||||
+ // if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|
||||
+ // return;
|
|
||||
+ // }
|
|
||||
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
|
||||
} |
|
||||
this._timeChanged = true; |
|
||||
diff --git a/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs b/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs
|
|
||||
index 7699ff6..01aad13 100644
|
|
||||
--- a/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs
|
|
||||
+++ b/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs
|
|
||||
@@ -951,9 +951,9 @@ class MatDatetimepickerClockComponent {
|
|
||||
value = 0; |
|
||||
} |
|
||||
// Don't close the minutes view if an invalid minute is clicked. |
|
||||
- if (!((_b = this._minutes.find((m) => (m === null || m === void 0 ? void 0 : m['value']) === value)) === null || _b === void 0 ? void 0 : _b['enabled'])) {
|
|
||||
- return;
|
|
||||
- }
|
|
||||
+ // if (!((_b = this._minutes.find((m) => (m === null || m === void 0 ? void 0 : m['value']) === value)) === null || _b === void 0 ? void 0 : _b['enabled'])) {
|
|
||||
+ // return;
|
|
||||
+ // }
|
|
||||
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
|
||||
} |
|
||||
this._timeChanged = true; |
|
||||
diff --git a/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs b/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs
|
|
||||
index 809a57d..f712b84 100644
|
|
||||
--- a/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs
|
|
||||
+++ b/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs
|
|
||||
@@ -946,9 +946,9 @@ class MatDatetimepickerClockComponent {
|
|
||||
value = 0; |
|
||||
} |
|
||||
// Don't close the minutes view if an invalid minute is clicked. |
|
||||
- if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|
||||
- return;
|
|
||||
- }
|
|
||||
+ // if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|
||||
+ // return;
|
|
||||
+ // }
|
|
||||
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
|
||||
} |
|
||||
this._timeChanged = true; |
|
||||
@ -0,0 +1,34 @@ |
|||||
|
diff --git a/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/clock.mjs b/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/clock.mjs
|
||||
|
index 7ecfae7..08363d3 100644
|
||||
|
--- a/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/clock.mjs
|
||||
|
+++ b/node_modules/@mat-datetimepicker/core/esm2022/datetimepicker/clock.mjs
|
||||
|
@@ -259,9 +259,9 @@ export class MatDatetimepickerClockComponent {
|
||||
|
value = 0; |
||||
|
} |
||||
|
// Don't close the minutes view if an invalid minute is clicked. |
||||
|
- if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
||||
|
- return;
|
||||
|
- }
|
||||
|
+ // if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
||||
|
+ // return;
|
||||
|
+ // }
|
||||
|
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
||||
|
} |
||||
|
this._timeChanged = true; |
||||
|
diff --git a/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs b/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs
|
||||
|
index 00f4a52..df688e3 100644
|
||||
|
--- a/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs
|
||||
|
+++ b/node_modules/@mat-datetimepicker/core/fesm2022/mat-datetimepicker-core.mjs
|
||||
|
@@ -946,9 +946,9 @@ class MatDatetimepickerClockComponent {
|
||||
|
value = 0; |
||||
|
} |
||||
|
// Don't close the minutes view if an invalid minute is clicked. |
||||
|
- if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
||||
|
- return;
|
||||
|
- }
|
||||
|
+ // if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
||||
|
+ // return;
|
||||
|
+ // }
|
||||
|
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
||||
|
} |
||||
|
this._timeChanged = true; |
||||
@ -1,15 +1,15 @@ |
|||||
diff --git a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
|
diff --git a/node_modules/angular-gridster2/fesm2022/angular-gridster2.mjs b/node_modules/angular-gridster2/fesm2022/angular-gridster2.mjs
|
||||
index cf4e220..df51c91 100644
|
index 0dcd873..e99b602 100644
|
||||
--- a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
|
--- a/node_modules/angular-gridster2/fesm2022/angular-gridster2.mjs
|
||||
+++ b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
|
+++ b/node_modules/angular-gridster2/fesm2022/angular-gridster2.mjs
|
||||
@@ -666,8 +666,8 @@ class GridsterRenderer {
|
@@ -666,8 +666,8 @@ class GridsterRenderer {
|
||||
renderer.setStyle(el, DirTypes.LTR ? 'margin-right' : 'margin-left', ''); |
renderer.setStyle(el, DirTypes.LTR ? 'margin-right' : 'margin-left', ''); |
||||
} |
} |
||||
else { |
else { |
||||
- const x = Math.round(this.gridster.curColWidth * item.x);
|
- const x = Math.round(this.gridster.curColWidth * item.x);
|
||||
- const y = Math.round(this.gridster.curRowHeight * item.y);
|
- const y = Math.round(this.gridster.curRowHeight * item.y);
|
||||
+ const x = this.gridster.curColWidth * item.x;
|
+ const x = this.gridster.curColWidth * item.x;
|
||||
+ const y = this.gridster.curRowHeight * item.y;
|
+ const y = this.gridster.curRowHeight * item.y;
|
||||
const width = this.gridster.curColWidth * item.cols - this.gridster.$options.margin; |
const width = this.gridster.curColWidth * item.cols - this.gridster.$options.margin; |
||||
const height = this.gridster.curRowHeight * item.rows - this.gridster.$options.margin; |
const height = this.gridster.curRowHeight * item.rows - this.gridster.$options.margin; |
||||
// set the cell style |
// set the cell style |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue