Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions container/features/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,13 @@
<artifactId>org.opennms.features.notifications.shell</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.opennms.features</groupId>
<artifactId>mib-compiler-rest</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
</dependencies>

<!-- override a bad repo that's causing issues -->
Expand Down
10 changes: 10 additions & 0 deletions container/features/src/main/resources/features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2135,5 +2135,15 @@
<bundle>mvn:org.apache.httpcomponents/httpclient-osgi/${httpclientVersion}</bundle>
<bundle>mvn:org.apache.httpcomponents/httpasyncclient-osgi/${httpasyncclientVersion}</bundle>
</feature>
<feature name="mib-compiler-rest"
version="${project.version}"
description="OpenNMS :: Features :: MIB Compiler REST">

<details>
REST interface for the OpenNMS MIB Compiler.
</details>
<bundle>mvn:org.opennms.features.mib-compiler-rest/org.opennms.features.mib-compiler-rest.parser/${project.version}</bundle>
<bundle>mvn:org.opennms.features.mib-compiler-rest/org.opennms.features.mib-compiler-rest.api/${project.version}</bundle>
</feature>

</features>
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ featuresBoot = ( \
opennms-config-management, \
opennms-scv-rest, \
scv-shell, \
mib-compiler-rest, \
opennms-karaf-health
# Ensure that the 'opennms-karaf-health' feature is installed *last*

Expand Down
90 changes: 90 additions & 0 deletions features/mib-compiler-rest/api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>org.opennms.features.mib-compiler-rest</artifactId>
<groupId>org.opennms.features</groupId>
<version>36.0.0-SNAPSHOT</version>
</parent>

<groupId>org.opennms.features.mib-compiler-rest</groupId>
<artifactId>org.opennms.features.mib-compiler-rest.api</artifactId>
<packaging>bundle</packaging>
<name>OpenNMS :: Features :: Mib Compiler Rest :: API</name>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-RequiredExecutionEnvironment>JavaSE-1.8</Bundle-RequiredExecutionEnvironment>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
<Karaf-Commands>*</Karaf-Commands>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>

<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>${cxfVersion}</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.opennms</groupId>
<artifactId>opennms-web-api</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opennms.features.mib-compiler-rest</groupId>
<artifactId>org.opennms.features.mib-compiler-rest.parser</artifactId>
<version>36.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.opennms.features.mibcompiler.rest.api;

import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.opennms.features.mibcompiler.rest.api.model.CompileMibRequest;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.util.List;

@Path("/mib-compiler")
public interface MibCompilerRestService {


@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces("application/json")
Response uploadMibFiles(@Multipart("upload") List<Attachment> attachments,
@Context SecurityContext securityContext) throws Exception;

@POST
@Path("/compile-mib")
@Consumes({MediaType.APPLICATION_JSON})
@Produces("application/json")
Response compilePendingMib(CompileMibRequest compileMibRequest, @Context SecurityContext securityContext) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package org.opennms.features.mibcompiler.rest.api.impl;

import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.opennms.features.mibcompiler.rest.api.MibCompilerRestService;
import org.opennms.features.mibcompiler.rest.api.model.CompileMibRequest;
import org.opennms.features.mibcompiler.rest.api.model.CompileMibResult;
import org.opennms.features.mibcompiler.rest.api.service.MibCompilerFileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.io.File;
import java.io.InputStream;
import java.util.*;


public class MibCompilerRestServiceImpl implements MibCompilerRestService {
private static final Logger LOG = LoggerFactory.getLogger(MibCompilerRestServiceImpl.class);


private final MibCompilerFileService mibCompilerFileService;

public MibCompilerRestServiceImpl(MibCompilerFileService mibCompilerFileService) {
this.mibCompilerFileService = mibCompilerFileService;
}


@Override
public Response uploadMibFiles(final List<Attachment> attachments, final SecurityContext securityContext) {
final Map<String, Attachment> fileMap = new LinkedHashMap<>();
for (final Attachment attachment : attachments) {
final String originalFilename = safeFilename(attachment);
final String baseName = MibCompilerFileService.stripPathAndExtension(originalFilename);

if (baseName == null || baseName.isBlank()) {
LOG.warn("Skipping attachment with invalid filename: {}", originalFilename);
continue;
}

if (fileMap.containsKey(baseName)) {
final String existingFilename = safeFilename(fileMap.get(baseName));
LOG.warn("Duplicate basename detected: '{}' and '{}' resolve to same name '{}'. Keeping first file.",
existingFilename, originalFilename, baseName);
continue;
}

fileMap.put(baseName, attachment);
}

final List<Map<String, Object>> successList = new ArrayList<>();
final List<Map<String, Object>> errorList = new ArrayList<>();

for (final Map.Entry<String, Attachment> entry : fileMap.entrySet()) {
final String baseName = entry.getKey();
final Attachment attachment = entry.getValue();
final String originalFilename = safeFilename(attachment);

try {
if (mibCompilerFileService.baseNameExistsInPendingOrCompiled(baseName)) {
errorList.add(Map.of(
"filename", originalFilename,
"basename", baseName,
"error", "A MIB with the same base name already exists in pending/ or compiled/."
));
continue;
}

final String ext = MibCompilerFileService.normalizeExtension(
getExtensionOrDefault(originalFilename, MibCompilerFileService.DEFAULT_MIB_EXTENSION),
MibCompilerFileService.DEFAULT_MIB_EXTENSION
);

try (InputStream in = attachment.getObject(InputStream.class)) {
final File saved = mibCompilerFileService.saveToPending(baseName, ext, in);
successList.add(Map.of(
"filename", originalFilename,
"savedAs", saved.getName(),
"success", true
));
}
} catch (Exception e) {
String message = e.getMessage();
if (message == null || message.isBlank()) {
message = "Unexpected error while processing MIB file.";
}
String detailedError = e.getClass().getSimpleName() + ": " + message;
errorList.add(Map.of(
"filename", originalFilename,
"basename", baseName,
"error", detailedError,
"exception", e.getClass().getName()
));
}
}

return Response.ok(Map.of("success", successList, "errors", errorList)).build();
}

@Override
public Response compilePendingMib(CompileMibRequest request, SecurityContext securityContext) throws Exception {
final String name = request != null ? request.getName() : null;

final CompileMibResult result = mibCompilerFileService.compilePendingByBaseName(name);

switch (result.getStatus()) {

case SUCCESS: {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", result.getMessage());
response.put("mibName", safeName(name));
response.put("compiledFile", fileNameOnly(result.getCompiledFile()));

return Response.ok(response).build();
}

case NOT_FOUND: {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", result.getMessage());
response.put("mibName", safeName(name));

return Response.status(Response.Status.NOT_FOUND)
.entity(response)
.build();
}

case INVALID_REQUEST: {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", result.getMessage());
response.put("mibName", safeName(name));

return Response.status(Response.Status.BAD_REQUEST)
.entity(response)
.build();
}

case MISSING_DEPENDENCIES:
case CONFLICT: {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", result.getMessage());
response.put("mibName", safeName(name));
response.put("missingDependencies", emptyToNull(result.getMissingDependencies()));

return Response.status(Response.Status.CONFLICT)
.entity(response)
.build();
}

case VALIDATION_FAILED: {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", result.getMessage());
response.put("mibName", safeName(name));
response.put("errors", result.getFormattedErrors());

return Response.status(Response.Status.BAD_REQUEST)
.entity(response)
.build();
}

default: {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "Unexpected status: " + result.getStatus());
response.put("mibName", safeName(name));

return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(response)
.build();
}
}
}

private static String safeName(String name) {
// keep response stable; avoid returning nulls when possible
return name == null ? "" : name;
}

private static String fileNameOnly(File f) {
return f == null ? null : f.getName();
}

private static List<String> emptyToNull(List<String> l) {
return (l == null || l.isEmpty()) ? null : l;
}


private static String safeFilename(final Attachment attachment) {
if (attachment == null || attachment.getContentDisposition() == null) {
return null;
}
return attachment.getContentDisposition().getParameter("filename");
}

private static String stripPathAndExtension(final String filename) {
if (filename == null) return null;

// Strip any path
String justName = filename;
int slash = justName.lastIndexOf('/');
int backslash = justName.lastIndexOf('\\');
int idx = Math.max(slash, backslash);
if (idx >= 0 && idx + 1 < justName.length()) {
justName = justName.substring(idx + 1);
}

justName = justName.trim();
if (justName.isEmpty()) return null;

// Strip extension
int dot = justName.lastIndexOf('.');
if (dot > 0) {
return justName.substring(0, dot);
}
return justName;
}

private static String getExtensionOrDefault(final String filename, final String defaultExt) {
if (filename == null) return defaultExt;
int dot = filename.lastIndexOf('.');
if (dot > 0 && dot < filename.length() - 1) {
return filename.substring(dot); // includes the "."
}
return defaultExt;
}
}
Loading
Loading