diff --git a/docs/modules/operation/pages/deep-dive/grpc-exporter/grpc-exporter.adoc b/docs/modules/operation/pages/deep-dive/grpc-exporter/grpc-exporter.adoc index f22cdf0148b3..e4aee4943171 100644 --- a/docs/modules/operation/pages/deep-dive/grpc-exporter/grpc-exporter.adoc +++ b/docs/modules/operation/pages/deep-dive/grpc-exporter/grpc-exporter.adoc @@ -13,17 +13,20 @@ See `monitored-services.proto` in the corresponding source distribution for the $ ssh -p 8101 admin@localhost ... admin@opennms()> config:edit org.opennms.features.grpc.exporter -admin@opennms()> config:property-set host bsm.onmshs.local:1440 <1> -admin@opennms()> config:property-set tls.cert.path /opt/opennms/etc/tls.cert <2> -admin@opennms()> config:property-set tls.enabled false <3> -admin@opennms()> config:property-set snapshot.interval 3600 false <4> +admin@opennms()> config:property-set host bsm.onmshs.local:1443 <1> +admin@opennms()> config:property-set tenant.id opennms-prime <2> +admin@opennms()> config:property-set tls.cert.path /opt/opennms/etc/tls.cert <3> +admin@opennms()> config:property-set tls.enabled false <4> +admin@opennms()> config:property-set snapshot.interval 3600 <5> admin@opennms()> config:update ---- <1> Set the hostname of the external gRPC application. -<2> Configure the path to the TLS certificate. -<3> TLS is enabled by default. For testing purposes, it can be disabled by setting this value to false. -<4> Set the interval (in seconds) at which the complete snapshot of services will be sent to the gRPC server. +<2> Set tenant id for the data being sent, defaults to `opennms-prime` +<3> Configure the path to the TLS certificate. +<4> TLS is enabled by default. For testing purposes, it can be disabled by setting this value to false. +<5> Set the interval (in seconds) at which the complete snapshot of services will be sent to the gRPC server, + defaults to 3600 secs. == Enable gRPC Exporter diff --git a/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdEntity.java b/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdEntity.java index 1c129f112366..d919e0a76ae5 100644 --- a/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdEntity.java +++ b/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdEntity.java @@ -239,6 +239,9 @@ public List evaluateAndCreateEvents(CollectionResourceWrapper resource, M // This reference contains the function that will be used by each evaluator to retrieve the status AtomicReference evaluateFunctionRef = new AtomicReference<>(null); + // compute scope here, see NMS-16966 + final Scope scope = getScopeForResource(resource); + // Depending on the type of threshold, we want to evaluate it differently // Threshold Values like value, rearm, trigger and expression are interpolated and cached in state so that // subsequent evaluations don't need to do any interpolation. @@ -250,7 +253,6 @@ public void visit(ThresholdConfigWrapper thresholdConfigWrapper) { ThresholdValuesSupplier thresholdValuesSupplier = new ThresholdValuesSupplier() { @Override public ThresholdEvaluatorState.ThresholdValues get() { - Scope scope = getScopeForResource(resource); ThresholdEvaluatorState.ThresholdValues thresholdValues = thresholdConfigWrapper.interpolateThresholdValues(scope); thresholdValues.setDsValue(computedValue); return thresholdValues; @@ -272,8 +274,6 @@ public void visit(ExpressionConfigWrapper expressionConfigWrapper) { // also retrieve the interpolated expression so we can persist it in our state going forward @Override public ExpressionConfigWrapper.ExpressionThresholdValues get() throws ThresholdExpressionException { - - Scope scope = getScopeForResource(resource); return expressionConfigWrapper .interpolateAndEvaluate(values, scope); } @@ -311,24 +311,28 @@ public double get(String evaluatedExpression) throws ThresholdExpressionExceptio return events; } - public Scope getScopeForResource(CollectionResourceWrapper resource) { + public static Scope getScopeForResource(EntityScopeProvider entityScopeProvider, CollectionResourceWrapper resource) { // Default to empty scopes and then attempt to populate each of node, interface, and service // scopes below Scope[] scopes = new Scope[]{EmptyScope.EMPTY, EmptyScope.EMPTY, EmptyScope.EMPTY}; if (resource != null) { - scopes[0] = m_entityScopeProvider.getScopeForNode(resource.getNodeId()); + scopes[0] = entityScopeProvider.getScopeForNode(resource.getNodeId()); String interfaceIp = resource.getHostAddress(); if (interfaceIp != null) { - scopes[1] = m_entityScopeProvider.getScopeForInterface(resource.getNodeId(), + scopes[1] = entityScopeProvider.getScopeForInterface(resource.getNodeId(), interfaceIp); - scopes[2] = m_entityScopeProvider.getScopeForService(resource.getNodeId(), + scopes[2] = entityScopeProvider.getScopeForService(resource.getNodeId(), InetAddresses.forString(interfaceIp), resource.getServiceName()); } } return new FallbackScope(scopes); } + public Scope getScopeForResource(CollectionResourceWrapper resource) { + return getScopeForResource(m_entityScopeProvider, resource); + } + /** *

addThreshold

* diff --git a/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdingSetImpl.java b/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdingSetImpl.java index dd79d78abe29..6fcae2f97015 100644 --- a/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdingSetImpl.java +++ b/features/collection/thresholding/impl/src/main/java/org/opennms/netmgt/threshd/ThresholdingSetImpl.java @@ -258,6 +258,9 @@ protected final List applyThresholds(CollectionResourceWrapper resourceWr LOG.debug("applyThresholds: Ignoring resource {} because required attributes map is empty.", resourceWrapper); return eventsList; } + // compute scope here, see NMS-16966 + final var scope = ThresholdEntity.getScopeForResource(m_entityScopeProvider, resourceWrapper); + LOG.debug("applyThresholds: Applying thresholds on {} using {} attributes.", resourceWrapper, attributesMap.size()); Date date = new Date(); synchronized(m_thresholdGroups) { @@ -285,8 +288,6 @@ protected final List applyThresholds(CollectionResourceWrapper resourceWr if(!valueMissing || relaxed) { LOG.info("applyThresholds: All attributes found for {}, evaluating", resourceWrapper); - final var scope = thresholdEntity.getScopeForResource(resourceWrapper); - resourceWrapper.setDsLabel(Interpolator.interpolate(thresholdEntity.getDatasourceLabel(), scope).output); try { List thresholdEvents = thresholdEntity.evaluateAndCreateEvents(resourceWrapper, values, date); @@ -314,6 +315,9 @@ protected boolean passedThresholdFilters(CollectionResourceWrapper resource, Thr return false; } + // compute scope here, see NMS-16966 + final var scope = ThresholdEntity.getScopeForResource(m_entityScopeProvider, resource); + // Find the filters for threshold definition for selected group/dataSource final List filters = thresholdEntity.getThresholdConfig().getBasethresholddef().getResourceFilters(); if (filters.size() == 0) return true; @@ -326,8 +330,6 @@ protected boolean passedThresholdFilters(CollectionResourceWrapper resource, Thr LOG.debug("passedThresholdFilters: filter #{}: field={}, regex='{}'", count, f.getField(), f.getContent().orElse(null)); count++; - final var scope = thresholdEntity.getScopeForResource(resource); - // Read Resource Attribute and apply filter rules if attribute is not null String attr = resource.getFieldValue(Interpolator.interpolate(f.getField(), scope).output); if (attr != null) { diff --git a/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcExporterClient.java b/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcExporterClient.java index abf4407f1f92..6f9d03be0b12 100644 --- a/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcExporterClient.java +++ b/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcExporterClient.java @@ -23,6 +23,7 @@ package org.opennms.features.grpc.exporter; import com.google.protobuf.Empty; +import io.grpc.ClientInterceptor; import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; @@ -49,7 +50,6 @@ public class GrpcExporterClient { public static final String FOREIGN_TYPE = "OpenNMS"; private final String host; - private final Integer port; private final String tlsCertPath; private final boolean tlsEnabled; @@ -63,27 +63,23 @@ public class GrpcExporterClient { private final AtomicBoolean reconnecting = new AtomicBoolean(false); private final AtomicBoolean stopped = new AtomicBoolean(false); private Callback inventoryCallback; + private final ClientInterceptor clientInterceptor; public GrpcExporterClient(final String host, - final Integer port, final String tlsCertPath, - final boolean tlsEnabled) { + final boolean tlsEnabled, + final ClientInterceptor clientInterceptor) { this.host = Objects.requireNonNull(host); - this.port = Objects.requireNonNull(port); this.tlsCertPath = tlsCertPath; this.tlsEnabled = tlsEnabled; + this.clientInterceptor = clientInterceptor; this.scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("grpc-exporter-connect")); } public void start() throws SSLException { - final NettyChannelBuilder channelBuilder; - if (this.port > 0) { - channelBuilder = NettyChannelBuilder.forAddress(this.host, this.port) + final NettyChannelBuilder channelBuilder = NettyChannelBuilder.forTarget(this.host) + .intercept(clientInterceptor) .keepAliveWithoutCalls(true); - } else { - channelBuilder = NettyChannelBuilder.forTarget(this.host) - .keepAliveWithoutCalls(true); - } if (tlsEnabled && tlsCertPath != null && !tlsCertPath.isBlank()) { channel = channelBuilder.useTransportSecurity() @@ -104,7 +100,7 @@ public void start() throws SSLException { this.monitoredServiceSyncStub = ServiceSyncGrpc.newStub(this.channel); connectStreams(); - LOG.info("GrpcExporterClient started connection to {}:{}", this.host, this.port); + LOG.info("GrpcExporterClient started connection to {}", this.host); } public void stop() { diff --git a/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcHeaderInterceptor.java b/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcHeaderInterceptor.java new file mode 100644 index 000000000000..06f3d5645ed2 --- /dev/null +++ b/features/grpc/exporter/src/main/java/org/opennms/features/grpc/exporter/GrpcHeaderInterceptor.java @@ -0,0 +1,63 @@ +/* + * Licensed to The OpenNMS Group, Inc (TOG) under one or more + * contributor license agreements. See the LICENSE.md file + * distributed with this work for additional information + * regarding copyright ownership. + * + * TOG licenses this file to You under the GNU Affero General + * Public License Version 3 (the "License") or (at your option) + * any later version. You may not use this file except in + * compliance with the License. You may obtain a copy of the + * License at: + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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.opennms.features.grpc.exporter; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; + +public class GrpcHeaderInterceptor implements ClientInterceptor { + + private final Metadata metadata; + + public GrpcHeaderInterceptor(String tenantId) { + metadata = new Metadata(); + metadata.put(Metadata.Key.of("tenant-id", Metadata.ASCII_STRING_MARSHALLER), tenantId); + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new HeaderAttachingClientCall<>(next.newCall(method, callOptions), metadata); + } + + private final static class HeaderAttachingClientCall + extends ForwardingClientCall.SimpleForwardingClientCall { + + private final Metadata metadata; + HeaderAttachingClientCall(ClientCall call, Metadata metadataToAttach) { + super(call); + this.metadata = metadataToAttach; + } + + @Override + public void start(Listener responseListener, Metadata headers) { + headers.merge(metadata); + super.start(responseListener, headers); + } + } +} diff --git a/features/grpc/exporter/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/features/grpc/exporter/src/main/resources/OSGI-INF/blueprint/blueprint.xml index cc32c85d2858..8cc3321b208c 100644 --- a/features/grpc/exporter/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/features/grpc/exporter/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -10,11 +10,11 @@ - - + + @@ -24,9 +24,13 @@ - + + + + + diff --git a/features/poller/api/pom.xml b/features/poller/api/pom.xml old mode 100644 new mode 100755 index f4afaa57cd82..676e23ec4903 --- a/features/poller/api/pom.xml +++ b/features/poller/api/pom.xml @@ -45,5 +45,24 @@ hibernate-jpa-2.0-api provided + + junit + junit + 4.13.2 + test + + + + org.eclipse.persistence + org.eclipse.persistence.core + ${eclipselinkVersion} + test + + + org.eclipse.persistence + org.eclipse.persistence.moxy + ${eclipselinkVersion} + test + - + \ No newline at end of file diff --git a/features/poller/api/src/main/java/org/opennms/netmgt/poller/DeviceConfig.java b/features/poller/api/src/main/java/org/opennms/netmgt/poller/DeviceConfig.java old mode 100644 new mode 100755 index 79943920e172..88df8979e2da --- a/features/poller/api/src/main/java/org/opennms/netmgt/poller/DeviceConfig.java +++ b/features/poller/api/src/main/java/org/opennms/netmgt/poller/DeviceConfig.java @@ -25,6 +25,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement(name="device-config") @XmlAccessorType(XmlAccessType.NONE) @@ -37,6 +38,7 @@ public class DeviceConfig { private String filename; @XmlAttribute(name="scriptOutput") + @XmlJavaTypeAdapter(EscapeSequenceAdapter.class) private String scriptOutput; public DeviceConfig(String scriptOutput) { diff --git a/features/poller/api/src/main/java/org/opennms/netmgt/poller/EscapeSequenceAdapter.java b/features/poller/api/src/main/java/org/opennms/netmgt/poller/EscapeSequenceAdapter.java new file mode 100755 index 000000000000..cf7f50848ac4 --- /dev/null +++ b/features/poller/api/src/main/java/org/opennms/netmgt/poller/EscapeSequenceAdapter.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2025 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2025 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.netmgt.poller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +public class EscapeSequenceAdapter extends XmlAdapter { + private static final Logger LOG = LoggerFactory.getLogger(EscapeSequenceAdapter.class); + + public EscapeSequenceAdapter() { + + } + + @Override + public String unmarshal(String v) throws Exception { + if (v != null) { + v = v.replace(" ", "\r") + .replace(" ", "\n"); + } + return v; + + } + + @Override + public String marshal(String v) throws Exception { + if (v != null) { + v = v.replace("\r", " ") + .replace("\n", " "); + } + return v; + + } +} diff --git a/features/poller/api/src/test/java/org/opennms/netmgt/poller/DeviceConfigTest.java b/features/poller/api/src/test/java/org/opennms/netmgt/poller/DeviceConfigTest.java new file mode 100644 index 000000000000..e8b5f19fff0b --- /dev/null +++ b/features/poller/api/src/test/java/org/opennms/netmgt/poller/DeviceConfigTest.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2025 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2025 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.netmgt.poller; + +import org.eclipse.persistence.jaxb.JAXBContext; +import org.eclipse.persistence.jaxb.JAXBContextFactory; +import org.junit.Test; + +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +public class DeviceConfigTest { + + @Test + public void testMarshalling() throws javax.xml.bind.JAXBException { + String scriptOutput = "HP J8692A Switch 3500yl-24G\r\r\nSoftware revision K.16.02.0026\r\r\n\r\r\n" + + " (C) Copyright 2018 Hewlett Packard Enterprise Development LP\r\n" + + "RESTRICTED RIGHTS LEGEND\r\n Confidential computer software. Valid license from Hewlett Packard Enterprise\r\n" + + "Development LP required for possession, use or copying. Consistent with FAR\r\n" + + "12.211 and 12.212, Commercial Computer Software, Computer Software\r\n" + + "Documentation, and Technical Data for Commercial Items are licensed to the\r\n" + + "U.S. Government under vendor's standard commercial license.\r\n" + + "^[[1;13r^[[1;1H^[[24;1HPress any key to continue\r\n" + + "^[[13;1H^[[?25h^[[24;27H^[[?6l^[[1;24r^[[?7h^[[2J^[[1;1H^[[1920;1920H^[[6n^[[1;1H"; + + DeviceConfig deviceConfig = new DeviceConfig(scriptOutput); + JAXBContext jaxbContext = (JAXBContext) JAXBContextFactory.createContext(new Class[]{DeviceConfig.class}, null); + + Marshaller marshaller = jaxbContext.createMarshaller(); + StringWriter stringWriter = new StringWriter(); + marshaller.marshal(deviceConfig, stringWriter); + String xmlOutput = stringWriter.toString(); + + assertNotNull(xmlOutput); + assertEquals("",xmlOutput); + + } + + @Test + public void testUnmarshalling() throws javax.xml.bind.JAXBException { + + String xmlInput = ""; + + JAXBContext jaxbContext = (JAXBContext) JAXBContextFactory.createContext(new Class[]{DeviceConfig.class}, null); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + + StringReader stringReader = new StringReader(xmlInput); + DeviceConfig deviceConfig = (DeviceConfig) unmarshaller.unmarshal(stringReader); + + String expectedOutputStartWith = "HP J8692A Switch 3500yl-24G\r\r\nSoftware revision K.16.02.0026\r\r\n\r\r\n" + + " (C) Copyright 2018 Hewlett Packard Enterprise Development LP\r\n" ; + String expectedOutputEndWith = "^[[1;13r^[[1;1H^[[24;1HPress any key to continue^[[13;1H^[[?25h^[[24;27H^[[?6l^[[1;24r^[[?7h^[[2J^[[1;1H^[[1920;1920H^[[6n^[[1;1H"; + String expectedOutputMiddleString = "12.211 and 12.212, Commercial Computer Software, Computer Software\r\n" + + "Documentation, and Technical Data for Commercial Items are licensed to the\r\n" ; + + assertNotNull(deviceConfig); + assertTrue(deviceConfig.getScriptOutput().contains(expectedOutputStartWith)); + assertTrue(deviceConfig.getScriptOutput().contains(expectedOutputMiddleString)); + assertTrue(deviceConfig.getScriptOutput().contains(expectedOutputEndWith)); + } +} diff --git a/features/poller/api/src/test/java/org/opennms/netmgt/poller/EscapeSequenceAdapterTest.java b/features/poller/api/src/test/java/org/opennms/netmgt/poller/EscapeSequenceAdapterTest.java new file mode 100755 index 000000000000..d9dad741687c --- /dev/null +++ b/features/poller/api/src/test/java/org/opennms/netmgt/poller/EscapeSequenceAdapterTest.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2025 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2025 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.netmgt.poller; + + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; + + +public class EscapeSequenceAdapterTest { + + private EscapeSequenceAdapter adapter; + + @Before + public void setUp() { + adapter = new EscapeSequenceAdapter(); + } + + @Test + public void testUnmarshalWithEscapeSequences() throws Exception { + String input = "Hello World Test"; + String expected = "Hello\rWorld\nTest"; + String result = adapter.unmarshal(input); + assertNotNull(result, "Result should not be null"); + assertEquals("Escape sequences should be replaced correctly", expected, result); + } + + @Test + public void testUnmarshalWithNullValue() throws Exception { + String result = adapter.unmarshal(null); + assertNull("Null input should return null", result); + } + + @Test + public void testMarshalWithSpecialCharacters() throws Exception { + String input = "Hello\rWorld\nTest"; + String expected = "Hello World Test"; + String result = adapter.marshal(input); + assertNotNull(result,"Result should not be null"); + assertEquals("Special characters should be converted to escape sequences",expected, result); + } + @Test + public void testMarshalWithNullValue() throws Exception { + String result = adapter.marshal(null); + assertNull("Null input should return null",result); + } + + @Test + public void testUnmarshalWithNoEscapeSequences() throws Exception { + String input = "NoEscapeSequenceHere"; + String expected = "NoEscapeSequenceHere"; + String result = adapter.unmarshal(input); + assertNotNull( "Result should not be null",result); + assertEquals("Input without escape sequences should remain unchanged",expected, result); + } + + @Test + public void testMarshalWithNoSpecialCharacters() throws Exception { + String input = "NormalString"; + String expected = "NormalString"; + String result = adapter.marshal(input); + assertNotNull( "Result should not be null",result); + assertEquals( "Input without special characters should remain unchanged",expected, result); + } +} diff --git a/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp b/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp index 97250b32cb3b..daf5e0022cda 100644 --- a/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp +++ b/opennms-webapp/src/main/webapp/admin/sched-outages/index.jsp @@ -25,6 +25,7 @@ import=" java.util.*, org.opennms.core.spring.BeanUtils, + org.opennms.core.utils.WebSecurityUtils, org.opennms.netmgt.config.*, org.opennms.netmgt.config.dao.outages.api.WriteablePollOutagesDao, org.opennms.netmgt.config.dao.thresholding.api.WriteableThreshdDao, @@ -184,7 +185,7 @@ List nodeList = pollOutagesDao.getNodeIds(outageName); for (int j = 0; j < nodeList.size(); j++) { OnmsNode elementNode = NetworkElementFactory.getInstance(getServletContext()).getNode(nodeList.get(j).getId()); - %>
  • <%=elementNode == null || elementNode.getType() == NodeType.DELETED ? "Node: Node ID " + nodeList.get(j).getId() + " Not Found" : "Node: " + elementNode.getLabel()%>
  • + %>
  • <%=elementNode == null || elementNode.getType() == NodeType.DELETED ? "Node: Node ID " + nodeList.get(j).getId() + " Not Found" : "Node: " + WebSecurityUtils.sanitizeString(elementNode.getLabel())%>
  • <% } List interfaceList = pollOutagesDao.getInterfaces(outageName); diff --git a/opennms-webapp/src/main/webapp/admin/sched-outages/jsonNodes.jsp b/opennms-webapp/src/main/webapp/admin/sched-outages/jsonNodes.jsp index df1ae3a45cc3..7b5816495e77 100644 --- a/opennms-webapp/src/main/webapp/admin/sched-outages/jsonNodes.jsp +++ b/opennms-webapp/src/main/webapp/admin/sched-outages/jsonNodes.jsp @@ -22,7 +22,7 @@ --%> <%@page language="java" - contentType="text/html" + contentType="application/json" session="true" import="java.util.*, java.util.regex.*, org.opennms.web.element.*,