All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.restconf.server.ModulesRequestProcessor Maven / Gradle / Ivy

/*
 * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.restconf.server;

import static org.opendaylight.restconf.server.Method.GET;
import static org.opendaylight.restconf.server.Method.HEAD;
import static org.opendaylight.restconf.server.Method.OPTIONS;
import static org.opendaylight.restconf.server.ResponseUtils.allowHeaderValue;
import static org.opendaylight.restconf.server.ResponseUtils.optionsResponse;
import static org.opendaylight.restconf.server.ResponseUtils.responseBuilder;
import static org.opendaylight.restconf.server.ResponseUtils.simpleErrorResponse;
import static org.opendaylight.restconf.server.ResponseUtils.simpleResponse;
import static org.opendaylight.restconf.server.ResponseUtils.unmappedRequestErrorResponse;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FutureCallback;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.AsciiString;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.FormattableBody;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.RestconfServer;
import org.opendaylight.restconf.server.api.ServerRequest;
import org.opendaylight.yangtools.yang.common.ErrorTag;

/**
 * Static request processor serving requests for yang-library-version and yang modules.
 *
 * @see RFC 8040.
 * Section 3.3.3. {+restconf}/yang-library-version
 */
final class ModulesRequestProcessor {
    @VisibleForTesting
    static final String REVISION = "revision";
    @VisibleForTesting
    static final String MISSING_FILENAME_ERROR = "Module name is missing";
    @VisibleForTesting
    static final String SOURCE_READ_FAILURE_ERROR = "Failure reading module source: ";
    @VisibleForTesting
    static final String ALLOW_METHODS = allowHeaderValue(OPTIONS, HEAD, GET);

    private ModulesRequestProcessor() {
        // hidden on purpose
    }

    static void processYangLibraryVersion(final RequestParameters params, final RestconfServer service,
            final FutureCallback callback) {
        switch (params.method()) {
            case OPTIONS -> callback.onSuccess(optionsResponse(params, ALLOW_METHODS));
            case HEAD, GET -> getYangLibraryVersion(params, service, callback);
            default -> callback.onSuccess(unmappedRequestErrorResponse(params));
        }
    }

    static void processModules(final RequestParameters params, final RestconfServer service,
            final FutureCallback callback) {
        switch (params.method()) {
            case OPTIONS -> callback.onSuccess(optionsResponse(params, ALLOW_METHODS));
            case HEAD, GET ->  getModule(params, service, callback);
            default -> callback.onSuccess(unmappedRequestErrorResponse(params));
        }
    }

    private static void getYangLibraryVersion(final RequestParameters params, final RestconfServer service,
            final FutureCallback callback) {
        final var request = new NettyServerRequest(params, callback,
            result -> responseBuilder(params, HttpResponseStatus.OK).setBody(result).build());
        service.yangLibraryVersionGET(request);
    }

    private static void getModule(final RequestParameters params, final RestconfServer service,
            final FutureCallback callback) {
        final var file = extractModuleFile(params.pathParameters().childIdentifier());
        final var revision = params.queryParameters().lookup(REVISION);
        if (file.name().isEmpty()) {
            callback.onSuccess(simpleErrorResponse(params, ErrorTag.MISSING_ELEMENT, MISSING_FILENAME_ERROR));
            return;
        }
        final var acceptYang = params.requestHeaders()
            .contains(HttpHeaderNames.ACCEPT, NettyMediaTypes.APPLICATION_YANG, true);
        final var acceptYin = params.requestHeaders()
            .contains(HttpHeaderNames.ACCEPT, NettyMediaTypes.APPLICATION_YIN_XML, true);
        if (acceptYin && !acceptYang) {
            // YIN if explicitly requested
            final var request = getModuleRequest(params, callback,NettyMediaTypes.APPLICATION_YIN_XML);
            if (file.mountPath.isEmpty()) {
                service.modulesYinGET(request, file.name(), revision);
            } else {
                service.modulesYinGET(request, file.mountPath(), file.name(), revision);
            }
        } else {
            // YANG by default, incl accept any
            final var request = getModuleRequest(params, callback,NettyMediaTypes.APPLICATION_YANG);
            if (file.mountPath.isEmpty()) {
                service.modulesYangGET(request, file.name(), revision);
            } else {
                service.modulesYangGET(request, file.mountPath(), file.name(), revision);
            }
        }
    }

    private static ServerRequest getModuleRequest(final RequestParameters params,
            final FutureCallback callback, final AsciiString mediaType) {
        return new NettyServerRequest<>(params, callback,
            result -> {
                final byte[] bytes;
                try {
                    bytes = result.source().asByteSource(StandardCharsets.UTF_8).read();
                } catch (IOException e) {
                    throw new ServerErrorException(ErrorTag.OPERATION_FAILED,
                       SOURCE_READ_FAILURE_ERROR + e.getMessage(), e);
                }
                return simpleResponse(params, HttpResponseStatus.OK, mediaType, bytes);
            });
    }

    private static ModuleFile extractModuleFile(final String path) {
        // optional mountPath followed by file name separated by slash
        final var lastIndex = path.length() - 1;
        final var splitIndex = path.lastIndexOf('/');
        if (splitIndex < 0) {
            return new ModuleFile(ApiPath.empty(), path);
        }
        final var apiPath = RequestUtils.extractApiPath(path.substring(0, splitIndex));
        final var name = splitIndex == lastIndex ? "" : path.substring(splitIndex + 1);
        return new ModuleFile(apiPath, name);
    }

    private record ModuleFile(ApiPath mountPath, String name) {
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy