Move AbstractBody et al.
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / api / AbstractBody.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.restconf.server.api;
9
10 import static java.util.Objects.requireNonNull;
11
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.lang.invoke.MethodHandles;
16 import java.lang.invoke.VarHandle;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.common.errors.RestconfError;
21 import org.opendaylight.yangtools.yang.data.api.YangNetconfError;
22 import org.opendaylight.yangtools.yang.data.api.YangNetconfErrorAware;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 /**
27  * An abstract request body backed by an {@link InputStream}.
28  */
29 public abstract sealed class AbstractBody implements AutoCloseable
30         permits ChildBody, DataPostBody, OperationInputBody, PatchBody, ResourceBody {
31     private static final Logger LOG = LoggerFactory.getLogger(AbstractBody.class);
32
33     private static final VarHandle INPUT_STREAM;
34
35     static {
36         try {
37             INPUT_STREAM = MethodHandles.lookup().findVarHandle(AbstractBody.class, "inputStream", InputStream.class);
38         } catch (NoSuchFieldException | IllegalAccessException e) {
39             throw new ExceptionInInitializerError(e);
40         }
41     }
42
43     @SuppressWarnings("unused")
44     @SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
45     private volatile InputStream inputStream;
46
47     AbstractBody(final InputStream inputStream) {
48         this.inputStream = requireNonNull(inputStream);
49     }
50
51     @Override
52     public final void close() {
53         final var is = getStream();
54         if (is != null) {
55             try {
56                 is.close();
57             } catch (IOException e) {
58                 LOG.info("Failed to close input", e);
59             }
60         }
61     }
62
63     final @NonNull InputStream acquireStream() {
64         final var is = getStream();
65         if (is == null) {
66             throw new IllegalStateException("Input stream has already been consumed");
67         }
68         return is;
69     }
70
71
72     /**
73      * Throw a {@link RestconfDocumentedException} if the specified exception has a {@link YangNetconfError} attachment.
74      *
75      * @param cause Proposed cause of a RestconfDocumentedException
76      */
77     static void throwIfYangError(final Exception cause) {
78         if (cause instanceof YangNetconfErrorAware infoAware) {
79             throw new RestconfDocumentedException(cause, infoAware.getNetconfErrors().stream()
80                 .map(error -> new RestconfError(error.type(), error.tag(), error.message(), error.appTag(),
81                     // FIXME: pass down error info
82                     null, error.path()))
83                 .toList());
84         }
85     }
86
87     private @Nullable InputStream getStream() {
88         return (InputStream) INPUT_STREAM.getAndSet(this, null);
89     }
90 }