use of org.openremote.model.attribute.Attribute in project openremote by openremote.
the class AbstractIOClientProtocol method getGenericStringEncodersAndDecoders.
/**
* Supplies a set of encoders/decoders that convert from/to {@link String} to/from {@link ByteBuf} based on the generic protocol {@link Attribute}s
*/
public static Supplier<ChannelHandler[]> getGenericStringEncodersAndDecoders(AbstractNettyIOClient<String, ?> client, IOAgent<?, ?, ?> agent) {
boolean hexMode = agent.getMessageConvertHex().orElse(false);
boolean binaryMode = agent.getMessageConvertBinary().orElse(false);
Charset charset = agent.getMessageCharset().map(Charset::forName).orElse(CharsetUtil.UTF_8);
int maxLength = agent.getMessageMaxLength().orElse(Integer.MAX_VALUE);
String[] delimiters = agent.getMessageDelimiters().orElse(new String[0]);
boolean stripDelimiter = agent.getMessageStripDelimiter().orElse(false);
return () -> {
List<ChannelHandler> encodersDecoders = new ArrayList<>();
if (hexMode || binaryMode) {
encodersDecoders.add(new AbstractNettyIOClient.MessageToByteEncoder<>(String.class, client, (msg, out) -> {
byte[] bytes = hexMode ? ProtocolUtil.bytesFromHexString(msg) : ProtocolUtil.bytesFromBinaryString(msg);
out.writeBytes(bytes);
}));
if (delimiters.length > 0) {
ByteBuf[] byteDelimiters = Arrays.stream(delimiters).map(delim -> Unpooled.wrappedBuffer(hexMode ? ProtocolUtil.bytesFromHexString(delim) : ProtocolUtil.bytesFromBinaryString(delim))).toArray(ByteBuf[]::new);
encodersDecoders.add(new DelimiterBasedFrameDecoder(maxLength, stripDelimiter, byteDelimiters));
} else {
encodersDecoders.add(new FixedLengthFrameDecoder(maxLength));
}
// Incoming messages will be bytes
encodersDecoders.add(new AbstractNettyIOClient.ByteToMessageDecoder<>(client, (byteBuf, messages) -> {
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
String msg = hexMode ? ProtocolUtil.bytesToHexString(bytes) : ProtocolUtil.bytesToBinaryString(bytes);
messages.add(msg);
}));
} else {
encodersDecoders.add(new StringEncoder(charset));
if (agent.getMessageMaxLength().isPresent()) {
encodersDecoders.add(new FixedLengthFrameDecoder(maxLength));
} else {
ByteBuf[] byteDelimiters;
if (delimiters.length > 0) {
byteDelimiters = Arrays.stream(delimiters).map(delim -> Unpooled.wrappedBuffer(delim.getBytes(charset))).toArray(ByteBuf[]::new);
} else {
byteDelimiters = Delimiters.lineDelimiter();
}
encodersDecoders.add(new DelimiterBasedFrameDecoder(maxLength, stripDelimiter, byteDelimiters));
}
encodersDecoders.add(new StringDecoder(charset));
encodersDecoders.add(new AbstractNettyIOClient.MessageToMessageDecoder<>(String.class, client));
}
return encodersDecoders.toArray(new ChannelHandler[0]);
};
}
use of org.openremote.model.attribute.Attribute in project openremote by openremote.
the class AssetStorageService method merge.
/**
* Merge the requested {@link Asset} checking that it meets all constraint requirements before doing so; the
* timestamp of each {@link Attribute} will also be updated to the current system time if it has changed to assist
* with {@link Attribute} equality (see {@link Attribute#equals}).
* @param overrideVersion If <code>true</code>, the merge will override the data in the database, independent of
* version.
* @param skipGatewayCheck Don't check if asset is a gateway asset and merge asset into local persistence service.
* @param userName the user which this asset needs to be assigned to.
* @return The current stored asset state.
* @throws IllegalArgumentException if the realm or parent is illegal, or other asset constraint is violated.
*/
@SuppressWarnings("unchecked")
public <T extends Asset<?>> T merge(T asset, boolean overrideVersion, boolean skipGatewayCheck, String userName) throws IllegalStateException, ConstraintViolationException {
return persistenceService.doReturningTransaction(em -> {
String gatewayId = gatewayService.getLocallyRegisteredGatewayId(asset.getId(), asset.getParentId());
if (!skipGatewayCheck && gatewayId != null) {
LOG.fine("Sending asset merge request to gateway: Gateway ID=" + gatewayId);
return gatewayService.mergeGatewayAsset(gatewayId, asset);
}
// Do standard JSR-380 validation on the asset (includes custom validation)
Set<ConstraintViolation<Asset<?>>> validationFailures = ValueUtil.validate(asset, Asset.AssetSave.class);
if (validationFailures.size() > 0) {
String msg = "Asset merge failed as asset has failed constraint validation: asset=" + asset;
ConstraintViolationException ex = new ConstraintViolationException(validationFailures);
LOG.log(Level.WARNING, msg + ", exception=" + ex.getMessage(), ex);
throw ex;
}
T existingAsset = TextUtil.isNullOrEmpty(asset.getId()) ? null : (T) em.find(Asset.class, asset.getId());
if (existingAsset != null) {
// Verify type has not been changed
if (!existingAsset.getType().equals(asset.getType())) {
String msg = "Asset type cannot be changed: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
if (!existingAsset.getRealm().equals(asset.getRealm())) {
String msg = "Asset realm cannot be changed: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
// Update timestamp on modified attributes this allows fast equality checking
asset.getAttributes().stream().forEach(attr -> {
existingAsset.getAttribute(attr.getName()).ifPresent(existingAttr -> {
// If attribute is modified make sure the timestamp is also updated to allow simple equality
if (!attr.deepEquals(existingAttr) && attr.getTimestamp().orElse(0L) <= existingAttr.getTimestamp().orElse(0L)) {
// In the unlikely situation that we are in the same millisecond as last update
// we will always ensure a delta of >= 1ms
attr.setTimestamp(Math.max(existingAttr.getTimestamp().orElse(0L) + 1, timerService.getCurrentTimeMillis()));
}
});
});
// concurrent updates
if (overrideVersion) {
asset.setVersion(existingAsset.getVersion());
}
}
// Validate realm
if (asset.getRealm() == null) {
String msg = "Asset realm must be set : asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
if (!identityService.getIdentityProvider().tenantExists(asset.getRealm())) {
String msg = "Asset realm not found or is inactive: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
if (asset.getParentId() != null && asset.getParentId().equals(asset.getId())) {
String msg = "Asset parent cannot be the asset: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
// Validate parent only if asset is new or parent has changed
if ((existingAsset == null && asset.getParentId() != null) || (existingAsset != null && asset.getParentId() != null && !asset.getParentId().equals(existingAsset.getParentId()))) {
Asset<?> parent = find(em, asset.getParentId(), true);
// The parent must exist
if (parent == null) {
String msg = "Asset parent not found: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
// The parent can not be a child of the asset
if (parent.pathContains(asset.getId())) {
String msg = "Asset parent cannot be a descendant of the asset: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
// The parent should be in the same realm
if (!parent.getRealm().equals(asset.getRealm())) {
String msg = "Asset parent must be in the same realm: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
// if parent is of type group then this child asset must have the correct type
if (parent instanceof GroupAsset) {
String childAssetType = parent.getAttributes().getValue(GroupAsset.CHILD_ASSET_TYPE).orElseThrow(() -> {
String msg = "Asset parent is of type GROUP but the childAssetType attribute is invalid: asset=" + asset;
LOG.info(msg);
return new IllegalStateException(msg);
});
// Look through type hierarchy for a match - this allows sub types
Class<?> clazz = asset.getClass();
boolean typeMatch = childAssetType.equals(clazz.getSimpleName());
while (!typeMatch && clazz != Asset.class) {
clazz = clazz.getSuperclass();
typeMatch = childAssetType.equals(clazz.getSimpleName());
}
if (!typeMatch) {
String msg = "Asset type does not match parent GROUP asset's childAssetType attribute: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
}
}
// Validate group child asset type attribute
if (asset instanceof GroupAsset) {
String childAssetType = ((GroupAsset) asset).getChildAssetType().map(childAssetTypeString -> TextUtil.isNullOrEmpty(childAssetTypeString) ? null : childAssetTypeString).orElseThrow(() -> {
String msg = "Asset of type GROUP childAssetType attribute must be a valid string: asset=" + asset;
LOG.info(msg);
return new IllegalStateException(msg);
});
String existingChildAssetType = existingAsset != null ? ((GroupAsset) existingAsset).getChildAssetType().orElseThrow(() -> {
String msg = "Asset of type GROUP childAssetType attribute must be a valid string: asset=" + asset;
LOG.info(msg);
return new IllegalStateException(msg);
}) : childAssetType;
if (!childAssetType.equals(existingChildAssetType)) {
String msg = "Asset of type GROUP so childAssetType attribute cannot be changed: asset=" + asset;
LOG.info(msg);
throw new IllegalStateException(msg);
}
}
// Update all empty attribute timestamps with server-time (a caller which doesn't have a
// reliable time source such as a browser should clear the timestamp when setting an attribute
// value).
asset.getAttributes().forEach(attribute -> {
if (!attribute.hasExplicitTimestamp()) {
attribute.setTimestamp(timerService.getCurrentTimeMillis());
}
});
// If username present
User user = null;
if (!TextUtil.isNullOrEmpty(userName)) {
user = identityService.getIdentityProvider().getUserByUsername(asset.getRealm(), userName);
if (user == null) {
String msg = "User not found: " + userName;
LOG.info(msg);
throw new IllegalStateException(msg);
}
}
T updatedAsset = em.merge(asset);
if (user != null) {
storeUserAssetLinks(em, Collections.singletonList(new UserAssetLink(user.getRealm(), user.getId(), updatedAsset.getId())));
}
return updatedAsset;
});
}
use of org.openremote.model.attribute.Attribute in project openremote by openremote.
the class ProtocolUtil method createGenericAttributeMessageConsumer.
public static Consumer<String> createGenericAttributeMessageConsumer(String assetId, Attribute<?> attribute, AgentLink<?> agentLink, Supplier<Long> currentMillisSupplier, Consumer<AttributeState> stateConsumer) {
ValueFilter[] matchFilters = agentLink.getMessageMatchFilters().orElse(null);
ValuePredicate matchPredicate = agentLink.getMessageMatchPredicate().orElse(null);
if (matchPredicate == null) {
return null;
}
return message -> {
if (!TextUtil.isNullOrEmpty(message)) {
Object messageFiltered = applyValueFilters(message, matchFilters);
if (messageFiltered != null) {
if (matchPredicate.asPredicate(currentMillisSupplier).test(messageFiltered)) {
Protocol.LOG.finest("Inbound message meets attribute matching meta so writing state to state consumer for attribute: asssetId=" + assetId + ", attribute=" + attribute.getName());
stateConsumer.accept(new AttributeState(assetId, attribute.getName(), message));
}
}
}
};
}
Aggregations