From df76d00eee18f54e45934a76cff79aa14c948445 Mon Sep 17 00:00:00 2001 From: Philipp <62473021+CrAfTsArMy@users.noreply.github.com> Date: Sat, 28 Mar 2026 13:29:39 +0100 Subject: [PATCH 1/2] Release 3.7.1 (#28) --- build.gradle | 4 +- .../de/craftsblock/craftsnet/CraftsNet.java | 2 +- .../craftsnet/addon/AddonManager.java | 73 +++++++++------ .../craftsnet/api/http/Response.java | 7 +- .../craftsnet/api/http/WebHandler.java | 87 ++++++++++++++---- .../api/http/{ => status}/HttpStatus.java | 12 ++- .../api/http/status/HttpStatusException.java | 92 +++++++++++++++++++ .../transformers/TransformerPerformer.java | 2 +- .../api/websocket/WebSocketClient.java | 2 +- .../utils/reflection/ReflectionUtils.java | 66 ++++++++++--- 10 files changed, 278 insertions(+), 69 deletions(-) rename src/main/java/de/craftsblock/craftsnet/api/http/{ => status}/HttpStatus.java (98%) create mode 100644 src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java diff --git a/build.gradle b/build.gradle index 63b6b357..cce8f7e8 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ application { } group = "de.craftsblock" -version = "3.7.0" +version = "3.7.1" repositories { mavenCentral() @@ -29,7 +29,7 @@ dependencies { // CraftsBlock related dependencies --------------------------------------------------------------------------------------- // https://repo.craftsblock.de/#/releases/de/craftsblock/craftscore/bom - implementation platform("de.craftsblock.craftscore:bom:3.8.13-pre8") + implementation platform("de.craftsblock.craftscore:bom:3.8.13-pre9") // https://repo.craftsblock.de/#/releases/de/craftsblock/craftscore/buffer api "de.craftsblock.craftscore:buffer" diff --git a/src/main/java/de/craftsblock/craftsnet/CraftsNet.java b/src/main/java/de/craftsblock/craftsnet/CraftsNet.java index 9901ef02..e8634090 100644 --- a/src/main/java/de/craftsblock/craftsnet/CraftsNet.java +++ b/src/main/java/de/craftsblock/craftsnet/CraftsNet.java @@ -54,7 +54,7 @@ public class CraftsNet { /** * The current version of CraftsNet. */ - public static final String version = "3.7.0"; + public static final String version = "3.7.1"; private static CraftsNet instance; diff --git a/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java b/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java index ac5623d0..69f81d2b 100644 --- a/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java +++ b/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java @@ -8,10 +8,9 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; +import org.jetbrains.annotations.UnmodifiableView; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -25,7 +24,7 @@ * * @author CraftsBlock * @author Philipp Maywald - * @version 1.3.3 + * @version 1.3.4 * @see Addon * @see AddonLoader * @since 1.0.0-SNAPSHOT @@ -37,7 +36,8 @@ public final class AddonManager { private final CraftsNet craftsNet; private final Logger logger; - private final ConcurrentHashMap addons = new ConcurrentHashMap<>(); + private final Map addons = new ConcurrentHashMap<>(); + private final Map addonsView = Collections.unmodifiableMap(addons); private final AddonLoader addonLoader; @@ -120,10 +120,14 @@ public void fromFiles() throws IOException { * Method to stop the AddonManager. It is called during application shutdown. */ public void stop() { - addons.values().forEach(addon -> { - logger.info("Disabling addon %s", addon.getName()); - this.unregister(addon); - }); + synchronized (addons) { + addons.values().forEach(addon -> { + logger.info("Disabling addon %s", addon.getName()); + addon.onDisable(); + }); + + addons.values().forEach(addon -> addons.remove(addon.getName())); + } craftsNet.getListenerRegistry().call(new AllAddonsDisabledEvent()); addons.clear(); @@ -135,7 +139,9 @@ public void stop() { * @param addon The addon to be registered. */ public void register(@NotNull Addon addon) { - addons.put(addon.getName(), addon); + synchronized (addons) { + addons.put(addon.getName(), addon); + } } /** @@ -144,8 +150,10 @@ public void register(@NotNull Addon addon) { * @param addon The addon to be unregistered. */ public void unregister(@NotNull Addon addon) { - addons.remove(addon.getName()); - addon.onDisable(); + synchronized (addons) { + addon.onDisable(); + addons.remove(addon.getName()); + } } /** @@ -153,8 +161,8 @@ public void unregister(@NotNull Addon addon) { * * @return A read-only ConcurrentHashMap containing the registered addons. */ - public @Unmodifiable @NotNull Map getAddons() { - return Collections.unmodifiableMap(addons); + public @NotNull @UnmodifiableView Map getAddons() { + return addonsView; } /** @@ -165,14 +173,17 @@ public void unregister(@NotNull Addon addon) { * @return An instance of the specified addon type if found, or {@code null} if not present. */ public @Nullable T getAddon(@NotNull Class addon) { - if (HollowAddon.class.isAssignableFrom(addon)) + if (HollowAddon.class.isAssignableFrom(addon)) { throw new IllegalArgumentException(addon.getSimpleName() + "s cannot be retrieved by class, use the name instead!"); + } - return addons.values().stream() - .filter(addon::isInstance) - .map(addon::cast) - .findFirst() - .orElse(null); + synchronized (addons) { + return addons.values().stream() + .filter(addon::isInstance) + .map(addon::cast) + .findFirst() + .orElse(null); + } } /** @@ -185,10 +196,12 @@ public void unregister(@NotNull Addon addon) { */ @SuppressWarnings("unchecked") public @Nullable T getAddon(String name) { - return (T) addons.values().stream() - .filter(addon -> addon.getName().equalsIgnoreCase(name)) - .findFirst() - .orElse(null); + synchronized (addons) { + return (T) addons.values().stream() + .filter(addon -> addon.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + } } /** @@ -199,8 +212,13 @@ public void unregister(@NotNull Addon addon) { * @since 3.3.5-SNAPSHOT */ public boolean isRegistered(@NotNull Class addon) { - if (HollowAddon.class.isAssignableFrom(addon)) return false; - return addons.values().stream().anyMatch(addon::isInstance); + if (HollowAddon.class.isAssignableFrom(addon)) { + return false; + } + + synchronized (addons) { + return addons.values().stream().anyMatch(addon::isInstance); + } } /** @@ -211,7 +229,10 @@ public boolean isRegistered(@NotNull Class addon) { * @since 3.3.5-SNAPSHOT */ public boolean isRegistered(@NotNull String name) { - return addons.values().stream().anyMatch(addon -> addon.getName().equalsIgnoreCase(name)); + synchronized (addons) { + return addons.values().stream() + .anyMatch(addon -> addon.getName().equalsIgnoreCase(name)); + } } /** diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/Response.java b/src/main/java/de/craftsblock/craftsnet/api/http/Response.java index ee8b2327..ce277fde 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/http/Response.java +++ b/src/main/java/de/craftsblock/craftsnet/api/http/Response.java @@ -9,7 +9,7 @@ import de.craftsblock.craftsnet.api.http.cookies.Cookie; import de.craftsblock.craftsnet.api.http.cors.CorsPolicy; import de.craftsblock.craftsnet.api.http.encoding.StreamEncoder; -import de.craftsblock.craftsnet.logging.Logger; +import de.craftsblock.craftsnet.api.http.status.HttpStatus; import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -288,11 +288,10 @@ public void redirect(@NotNull String url) { *

The {@code Location} header instructs the client to perform a new request * to the specified resource.

* - * @param url The target URL to which the client should be redirected. - * Must not be {@code null}. + * @param url The target URL to which the client should be redirected. + * Must not be {@code null}. * @param redirection the redirection status to use. Must be one of the * {@link HttpStatus.Redirection} values. - * * @throws IllegalStateException if the response headers have already been sent * and the redirect can no longer be applied */ diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java b/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java index 71644104..ab515302 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java +++ b/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java @@ -11,12 +11,14 @@ import de.craftsblock.craftsnet.api.http.encoding.StreamEncoder; import de.craftsblock.craftsnet.api.http.encoding.StreamEncoderRegistry; import de.craftsblock.craftsnet.api.http.encoding.builtin.IdentityStreamEncoder; +import de.craftsblock.craftsnet.api.http.status.HttpStatus; +import de.craftsblock.craftsnet.api.http.status.HttpStatusException; import de.craftsblock.craftsnet.api.middlewares.Middleware; import de.craftsblock.craftsnet.api.middlewares.MiddlewareCallbackInfo; import de.craftsblock.craftsnet.api.session.Session; import de.craftsblock.craftsnet.api.session.SessionInfo; -import de.craftsblock.craftsnet.api.utils.Context; import de.craftsblock.craftsnet.api.transformers.TransformerPerformer; +import de.craftsblock.craftsnet.api.utils.Context; import de.craftsblock.craftsnet.api.utils.ProtocolVersion; import de.craftsblock.craftsnet.api.utils.Scheme; import de.craftsblock.craftsnet.events.requests.PostRequestEvent; @@ -25,7 +27,6 @@ import de.craftsblock.craftsnet.events.requests.shares.ShareFileLoadedEvent; import de.craftsblock.craftsnet.events.requests.shares.ShareRequestEvent; import de.craftsblock.craftsnet.logging.Logger; -import de.craftsblock.craftsnet.utils.Utils; import de.craftsblock.craftsnet.utils.reflection.ReflectionUtils; import java.io.IOException; @@ -35,6 +36,7 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -137,22 +139,7 @@ public void handle(HttpExchange httpExchange) throws IOException { craftsNet.getListenerRegistry().call(new PostRequestEvent(exchange, result.getKey(), result.getValue())); } } catch (Throwable t) { - if (craftsNet.getLogStream() != null) { - long errorID = craftsNet.getLogStream().createErrorLog(this.craftsNet, t, this.scheme.getName(), url); - logger.error("Error: %s", t, errorID); - if (!response.headersSent()) { - response.setStatus(500); - } - - if (!response.sendingFile() && !httpMethod.equals(HttpMethod.HEAD) && !httpMethod.equals(HttpMethod.UNKNOWN)) { - response.print(Json.empty() - .set("status", "500") - .set("message", "An unexpected exception happened whilst processing your request!") - .set("incident", errorID)); - } - } else { - logger.error(t); - } + handleThrowable(response, url, httpMethod, t); } finally { response.close(); } @@ -161,6 +148,70 @@ public void handle(HttpExchange httpExchange) throws IOException { } } + /** + * Handles a {@link Throwable}. + * + * @param response The {@link Response} in which context the throwable was thrown. + * @param url The url of the request. + * @param httpMethod The {@link HttpMethod} of the request. + * @param t The {@link Throwable} that has been caught. + * @since 3.7.1 + */ + private void handleThrowable(Response response, String url, HttpMethod httpMethod, Throwable t) { + BiConsumer responder = (json, code) -> { + if (!response.headersSent()) { + response.setStatus(code); + } + + if (!response.sendingFile() && !httpMethod.equals(HttpMethod.HEAD) && !httpMethod.equals(HttpMethod.UNKNOWN)) { + response.print(json); + } + }; + + if (t instanceof HttpStatusException httpStatusException) { + handleHttpStatusException(responder, httpStatusException); + return; + } + + if (t.getCause() instanceof HttpStatusException httpStatusException) { + handleHttpStatusException(responder, httpStatusException); + return; + } + + Long errorID; + if (craftsNet.getLogStream() != null) { + errorID = craftsNet.getLogStream().createErrorLog(this.craftsNet, t, this.scheme.getName(), url); + logger.error("Error: %s", t, errorID); + } else { + errorID = null; + logger.error(t); + } + + responder.accept( + Json.empty() + .set("code", HttpStatus.ServerError.INTERNAL_SERVER_ERROR.getCode()) + .set("message", "An unexpected exception happened whilst processing your request!") + .setIf("incident", errorID, () -> errorID != null), + HttpStatus.ServerError.INTERNAL_SERVER_ERROR.getCode() + ); + } + + /** + * Handles caught {@link HttpStatusException}'s. + * + * @param responder Responds to the {@link Response}. + * @param statusException The caught {@link HttpStatusException}. + * @since 3.7.1 + */ + private void handleHttpStatusException(BiConsumer responder, HttpStatusException statusException) { + responder.accept( + Json.empty() + .set("code", statusException.getCode()) + .set("message", statusException.getMessage()), + statusException.getCode() + ); + } + /** * Handles a http request and determines whether a valid route or share is available * for the given exchange. If a matching route or share is found, the appropriate handler diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/HttpStatus.java b/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatus.java similarity index 98% rename from src/main/java/de/craftsblock/craftsnet/api/http/HttpStatus.java rename to src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatus.java index daeb61db..b2ddcc03 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/http/HttpStatus.java +++ b/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatus.java @@ -1,4 +1,4 @@ -package de.craftsblock.craftsnet.api.http; +package de.craftsblock.craftsnet.api.http.status; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; @@ -118,6 +118,16 @@ default boolean isServerError() { return isCategory(5); } + /** + * Checks whether this status indicates an error. + * + * @return {@code true} if the status code indicates an error. + * @since 3.7.1 + */ + default boolean isError() { + return isClientError() || isServerError(); + } + /** * Checks whether this HTTP status is a custom status. * diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java b/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java new file mode 100644 index 00000000..77ef1410 --- /dev/null +++ b/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java @@ -0,0 +1,92 @@ +package de.craftsblock.craftsnet.api.http.status; + +import org.jetbrains.annotations.NotNull; + +/** + * Exception that represents an HTTP-specific condition. + *

+ * This exception encapsulates a {@link HttpStatus} which describes + * the HTTP status code and reason associated with the error. + * It can be used to propagate HTTP-related failures through the + * application stack in a structured way. + *

+ * Depending on the constructor used, a custom message and/or cause + * can be provided. If no custom message is specified, the default + * reason phrase of the {@link HttpStatus} will be used. + * + * @author Philipp Maywald + * @author CraftsBlock + * @version 1.0.0 + * @see HttpStatus + * @since 3.7.1 + */ +public class HttpStatusException extends RuntimeException { + + private final HttpStatus status; + + /** + * Creates a new {@link HttpStatusException} using the given status. + *

+ * The exception message will default to the reason phrase of the status. + * + * @param status The HTTP status associated with this exception. + */ + public HttpStatusException(@NotNull HttpStatus status) { + this(status, status.getReason()); + } + + /** + * Creates a new {@link HttpStatusException} with a cause. + *

+ * The exception message will default to the reason phrase of the status. + * + * @param status The HTTP status associated with this exception. + * @param cause The underlying cause of this exception. + */ + public HttpStatusException(@NotNull HttpStatus status, @NotNull Throwable cause) { + this(status, status.getReason(), cause); + } + + /** + * Creates a new {@link HttpStatusException} with a custom message. + * + * @param status The HTTP status associated with this exception. + * @param message The detail message describing the error. + */ + public HttpStatusException(@NotNull HttpStatus status, @NotNull String message) { + super(message); + this.status = status; + } + + /** + * Creates a new {@link HttpStatusException} with a custom message and cause. + * + * @param status The HTTP status associated with this exception. + * @param message The detail message describing the error. + * @param cause The underlying cause of this exception. + */ + public HttpStatusException(@NotNull HttpStatus status, @NotNull String message, + @NotNull Throwable cause) { + super(message, cause); + this.status = status; + } + + /** + * Returns the {@link HttpStatus} associated with this exception. + * + * @return The HTTP status. + */ + public HttpStatus getStatus() { + return status; + } + + /** + * Returns the numeric HTTP status code. + * + * @return The HTTP status code. + */ + public int getCode() { + return status.getCode(); + } + +} \ No newline at end of file diff --git a/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java b/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java index 2b0b456d..62fc4320 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java +++ b/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java @@ -260,7 +260,7 @@ private Object transform(String parameter, Transformer transformer, Class getCallerClass() { * @since 3.3.1-SNAPSHOT */ public static Class getCallerClass(@Range(from = 1, to = Integer.MAX_VALUE) int level) { - return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) - .walk(frames -> frames.skip(level) - .findFirst() - .map(StackWalker.StackFrame::getDeclaringClass) - .orElseThrow() - ); + return STACK_WALKER.walk(frames -> frames.skip(level) + .findFirst() + .map(StackWalker.StackFrame::getDeclaringClass) + .orElseThrow(() -> new IllegalStateException("No stack frame found for caller level " + level)) + ); } /** @@ -87,6 +88,45 @@ public static void restrictToCallers(Class... allowed) { throw new IllegalStateException(callersCaller.getName() + " is not permitted to call a " + caller.getSimpleName()); } + /** + * Rethrows a throwable while unpacking {@link InvocationTargetException}. + * + * @param throwable The {@link InvocationTargetException} that was caught. + * @param The excepted return type used for automatically adjusting to caller method. + * @return Nothing, just for returning in case the calling method needs it. + * @since 3.7.1 + */ + @Contract("_ -> fail") + public static T rethrowReflectionThrowable(Throwable throwable) { + return rethrowReflectionThrowable(throwable, null); + } + + /** + * Rethrows a throwable while unpacking {@link InvocationTargetException}. + * + * @param throwable The {@link InvocationTargetException} that was caught. + * @param alternativeMessage An alternative message for the fallback exception. + * @param The excepted return type used for automatically adjusting to caller method. + * @return Nothing, just for returning in case the calling method needs it. + * @since 3.7.1 + */ + @Contract("_, _ -> fail") + public static T rethrowReflectionThrowable(Throwable throwable, String alternativeMessage) { + if (throwable instanceof InvocationTargetException invocationTargetException) { + return rethrowReflectionThrowable(invocationTargetException, alternativeMessage); + } + + if (throwable instanceof UndeclaredThrowableException undeclaredThrowableException) { + throw undeclaredThrowableException; + } + + if (throwable instanceof RuntimeException runtimeException) { + throw runtimeException; + } + + throw new UndeclaredThrowableException(throwable, alternativeMessage); + } + /** * Checks if a constructor is present in the specified class with the provided argument types. * @@ -147,7 +187,7 @@ public static Constructor findConstructor(Class clazz, Class... arg .unreflectConstructor(constructor) .invokeWithArguments(args); } catch (Throwable e) { - throw new RuntimeException("Could not create instance of " + type.getSimpleName(), e); + return rethrowReflectionThrowable(e, "Could not create instance of " + type.getSimpleName()); } } @@ -165,11 +205,9 @@ public static void setField(String name, Object target, Object value) { .unreflectVarHandle(field) .set(target, value); } catch (Throwable e) { - throw new RuntimeException( - "Can not set field %s of class %s!".formatted( - name, target.getClass().getSimpleName() - ), e - ); + rethrowReflectionThrowable(e, "Can not set field %s of class %s!".formatted( + name, target.getClass().getSimpleName() + )); } } @@ -239,9 +277,7 @@ public static Object invokeMethod(Object owner, Method method, Object... args) { ? handle.bindTo(owner) : handle).invokeWithArguments(args); } catch (Throwable e) { - throw new RuntimeException( - "Could not invoke " + method.toGenericString(), e - ); + return rethrowReflectionThrowable(e, "Could not invoke " + method.toGenericString()); } } From 0cc089ad6a6b68049009ec7678905a5a9f8615b5 Mon Sep 17 00:00:00 2001 From: Philipp <62473021+CrAfTsArMy@users.noreply.github.com> Date: Sat, 28 Mar 2026 13:30:14 +0100 Subject: [PATCH 2/2] Revert "Release 3.7.1" (#29) --- build.gradle | 4 +- .../de/craftsblock/craftsnet/CraftsNet.java | 2 +- .../craftsnet/addon/AddonManager.java | 73 ++++++--------- .../api/http/{status => }/HttpStatus.java | 12 +-- .../craftsnet/api/http/Response.java | 7 +- .../craftsnet/api/http/WebHandler.java | 87 ++++-------------- .../api/http/status/HttpStatusException.java | 92 ------------------- .../transformers/TransformerPerformer.java | 2 +- .../api/websocket/WebSocketClient.java | 2 +- .../utils/reflection/ReflectionUtils.java | 66 +++---------- 10 files changed, 69 insertions(+), 278 deletions(-) rename src/main/java/de/craftsblock/craftsnet/api/http/{status => }/HttpStatus.java (98%) delete mode 100644 src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java diff --git a/build.gradle b/build.gradle index cce8f7e8..63b6b357 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ application { } group = "de.craftsblock" -version = "3.7.1" +version = "3.7.0" repositories { mavenCentral() @@ -29,7 +29,7 @@ dependencies { // CraftsBlock related dependencies --------------------------------------------------------------------------------------- // https://repo.craftsblock.de/#/releases/de/craftsblock/craftscore/bom - implementation platform("de.craftsblock.craftscore:bom:3.8.13-pre9") + implementation platform("de.craftsblock.craftscore:bom:3.8.13-pre8") // https://repo.craftsblock.de/#/releases/de/craftsblock/craftscore/buffer api "de.craftsblock.craftscore:buffer" diff --git a/src/main/java/de/craftsblock/craftsnet/CraftsNet.java b/src/main/java/de/craftsblock/craftsnet/CraftsNet.java index e8634090..9901ef02 100644 --- a/src/main/java/de/craftsblock/craftsnet/CraftsNet.java +++ b/src/main/java/de/craftsblock/craftsnet/CraftsNet.java @@ -54,7 +54,7 @@ public class CraftsNet { /** * The current version of CraftsNet. */ - public static final String version = "3.7.1"; + public static final String version = "3.7.0"; private static CraftsNet instance; diff --git a/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java b/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java index 69f81d2b..ac5623d0 100644 --- a/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java +++ b/src/main/java/de/craftsblock/craftsnet/addon/AddonManager.java @@ -8,9 +8,10 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnmodifiableView; +import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -24,7 +25,7 @@ * * @author CraftsBlock * @author Philipp Maywald - * @version 1.3.4 + * @version 1.3.3 * @see Addon * @see AddonLoader * @since 1.0.0-SNAPSHOT @@ -36,8 +37,7 @@ public final class AddonManager { private final CraftsNet craftsNet; private final Logger logger; - private final Map addons = new ConcurrentHashMap<>(); - private final Map addonsView = Collections.unmodifiableMap(addons); + private final ConcurrentHashMap addons = new ConcurrentHashMap<>(); private final AddonLoader addonLoader; @@ -120,14 +120,10 @@ public void fromFiles() throws IOException { * Method to stop the AddonManager. It is called during application shutdown. */ public void stop() { - synchronized (addons) { - addons.values().forEach(addon -> { - logger.info("Disabling addon %s", addon.getName()); - addon.onDisable(); - }); - - addons.values().forEach(addon -> addons.remove(addon.getName())); - } + addons.values().forEach(addon -> { + logger.info("Disabling addon %s", addon.getName()); + this.unregister(addon); + }); craftsNet.getListenerRegistry().call(new AllAddonsDisabledEvent()); addons.clear(); @@ -139,9 +135,7 @@ public void stop() { * @param addon The addon to be registered. */ public void register(@NotNull Addon addon) { - synchronized (addons) { - addons.put(addon.getName(), addon); - } + addons.put(addon.getName(), addon); } /** @@ -150,10 +144,8 @@ public void register(@NotNull Addon addon) { * @param addon The addon to be unregistered. */ public void unregister(@NotNull Addon addon) { - synchronized (addons) { - addon.onDisable(); - addons.remove(addon.getName()); - } + addons.remove(addon.getName()); + addon.onDisable(); } /** @@ -161,8 +153,8 @@ public void unregister(@NotNull Addon addon) { * * @return A read-only ConcurrentHashMap containing the registered addons. */ - public @NotNull @UnmodifiableView Map getAddons() { - return addonsView; + public @Unmodifiable @NotNull Map getAddons() { + return Collections.unmodifiableMap(addons); } /** @@ -173,17 +165,14 @@ public void unregister(@NotNull Addon addon) { * @return An instance of the specified addon type if found, or {@code null} if not present. */ public @Nullable T getAddon(@NotNull Class addon) { - if (HollowAddon.class.isAssignableFrom(addon)) { + if (HollowAddon.class.isAssignableFrom(addon)) throw new IllegalArgumentException(addon.getSimpleName() + "s cannot be retrieved by class, use the name instead!"); - } - synchronized (addons) { - return addons.values().stream() - .filter(addon::isInstance) - .map(addon::cast) - .findFirst() - .orElse(null); - } + return addons.values().stream() + .filter(addon::isInstance) + .map(addon::cast) + .findFirst() + .orElse(null); } /** @@ -196,12 +185,10 @@ public void unregister(@NotNull Addon addon) { */ @SuppressWarnings("unchecked") public @Nullable T getAddon(String name) { - synchronized (addons) { - return (T) addons.values().stream() - .filter(addon -> addon.getName().equalsIgnoreCase(name)) - .findFirst() - .orElse(null); - } + return (T) addons.values().stream() + .filter(addon -> addon.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); } /** @@ -212,13 +199,8 @@ public void unregister(@NotNull Addon addon) { * @since 3.3.5-SNAPSHOT */ public boolean isRegistered(@NotNull Class addon) { - if (HollowAddon.class.isAssignableFrom(addon)) { - return false; - } - - synchronized (addons) { - return addons.values().stream().anyMatch(addon::isInstance); - } + if (HollowAddon.class.isAssignableFrom(addon)) return false; + return addons.values().stream().anyMatch(addon::isInstance); } /** @@ -229,10 +211,7 @@ public boolean isRegistered(@NotNull Class addon) { * @since 3.3.5-SNAPSHOT */ public boolean isRegistered(@NotNull String name) { - synchronized (addons) { - return addons.values().stream() - .anyMatch(addon -> addon.getName().equalsIgnoreCase(name)); - } + return addons.values().stream().anyMatch(addon -> addon.getName().equalsIgnoreCase(name)); } /** diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatus.java b/src/main/java/de/craftsblock/craftsnet/api/http/HttpStatus.java similarity index 98% rename from src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatus.java rename to src/main/java/de/craftsblock/craftsnet/api/http/HttpStatus.java index b2ddcc03..daeb61db 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatus.java +++ b/src/main/java/de/craftsblock/craftsnet/api/http/HttpStatus.java @@ -1,4 +1,4 @@ -package de.craftsblock.craftsnet.api.http.status; +package de.craftsblock.craftsnet.api.http; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; @@ -118,16 +118,6 @@ default boolean isServerError() { return isCategory(5); } - /** - * Checks whether this status indicates an error. - * - * @return {@code true} if the status code indicates an error. - * @since 3.7.1 - */ - default boolean isError() { - return isClientError() || isServerError(); - } - /** * Checks whether this HTTP status is a custom status. * diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/Response.java b/src/main/java/de/craftsblock/craftsnet/api/http/Response.java index ce277fde..ee8b2327 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/http/Response.java +++ b/src/main/java/de/craftsblock/craftsnet/api/http/Response.java @@ -9,7 +9,7 @@ import de.craftsblock.craftsnet.api.http.cookies.Cookie; import de.craftsblock.craftsnet.api.http.cors.CorsPolicy; import de.craftsblock.craftsnet.api.http.encoding.StreamEncoder; -import de.craftsblock.craftsnet.api.http.status.HttpStatus; +import de.craftsblock.craftsnet.logging.Logger; import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -288,10 +288,11 @@ public void redirect(@NotNull String url) { *

The {@code Location} header instructs the client to perform a new request * to the specified resource.

* - * @param url The target URL to which the client should be redirected. - * Must not be {@code null}. + * @param url The target URL to which the client should be redirected. + * Must not be {@code null}. * @param redirection the redirection status to use. Must be one of the * {@link HttpStatus.Redirection} values. + * * @throws IllegalStateException if the response headers have already been sent * and the redirect can no longer be applied */ diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java b/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java index ab515302..71644104 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java +++ b/src/main/java/de/craftsblock/craftsnet/api/http/WebHandler.java @@ -11,14 +11,12 @@ import de.craftsblock.craftsnet.api.http.encoding.StreamEncoder; import de.craftsblock.craftsnet.api.http.encoding.StreamEncoderRegistry; import de.craftsblock.craftsnet.api.http.encoding.builtin.IdentityStreamEncoder; -import de.craftsblock.craftsnet.api.http.status.HttpStatus; -import de.craftsblock.craftsnet.api.http.status.HttpStatusException; import de.craftsblock.craftsnet.api.middlewares.Middleware; import de.craftsblock.craftsnet.api.middlewares.MiddlewareCallbackInfo; import de.craftsblock.craftsnet.api.session.Session; import de.craftsblock.craftsnet.api.session.SessionInfo; -import de.craftsblock.craftsnet.api.transformers.TransformerPerformer; import de.craftsblock.craftsnet.api.utils.Context; +import de.craftsblock.craftsnet.api.transformers.TransformerPerformer; import de.craftsblock.craftsnet.api.utils.ProtocolVersion; import de.craftsblock.craftsnet.api.utils.Scheme; import de.craftsblock.craftsnet.events.requests.PostRequestEvent; @@ -27,6 +25,7 @@ import de.craftsblock.craftsnet.events.requests.shares.ShareFileLoadedEvent; import de.craftsblock.craftsnet.events.requests.shares.ShareRequestEvent; import de.craftsblock.craftsnet.logging.Logger; +import de.craftsblock.craftsnet.utils.Utils; import de.craftsblock.craftsnet.utils.reflection.ReflectionUtils; import java.io.IOException; @@ -36,7 +35,6 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -139,7 +137,22 @@ public void handle(HttpExchange httpExchange) throws IOException { craftsNet.getListenerRegistry().call(new PostRequestEvent(exchange, result.getKey(), result.getValue())); } } catch (Throwable t) { - handleThrowable(response, url, httpMethod, t); + if (craftsNet.getLogStream() != null) { + long errorID = craftsNet.getLogStream().createErrorLog(this.craftsNet, t, this.scheme.getName(), url); + logger.error("Error: %s", t, errorID); + if (!response.headersSent()) { + response.setStatus(500); + } + + if (!response.sendingFile() && !httpMethod.equals(HttpMethod.HEAD) && !httpMethod.equals(HttpMethod.UNKNOWN)) { + response.print(Json.empty() + .set("status", "500") + .set("message", "An unexpected exception happened whilst processing your request!") + .set("incident", errorID)); + } + } else { + logger.error(t); + } } finally { response.close(); } @@ -148,70 +161,6 @@ public void handle(HttpExchange httpExchange) throws IOException { } } - /** - * Handles a {@link Throwable}. - * - * @param response The {@link Response} in which context the throwable was thrown. - * @param url The url of the request. - * @param httpMethod The {@link HttpMethod} of the request. - * @param t The {@link Throwable} that has been caught. - * @since 3.7.1 - */ - private void handleThrowable(Response response, String url, HttpMethod httpMethod, Throwable t) { - BiConsumer responder = (json, code) -> { - if (!response.headersSent()) { - response.setStatus(code); - } - - if (!response.sendingFile() && !httpMethod.equals(HttpMethod.HEAD) && !httpMethod.equals(HttpMethod.UNKNOWN)) { - response.print(json); - } - }; - - if (t instanceof HttpStatusException httpStatusException) { - handleHttpStatusException(responder, httpStatusException); - return; - } - - if (t.getCause() instanceof HttpStatusException httpStatusException) { - handleHttpStatusException(responder, httpStatusException); - return; - } - - Long errorID; - if (craftsNet.getLogStream() != null) { - errorID = craftsNet.getLogStream().createErrorLog(this.craftsNet, t, this.scheme.getName(), url); - logger.error("Error: %s", t, errorID); - } else { - errorID = null; - logger.error(t); - } - - responder.accept( - Json.empty() - .set("code", HttpStatus.ServerError.INTERNAL_SERVER_ERROR.getCode()) - .set("message", "An unexpected exception happened whilst processing your request!") - .setIf("incident", errorID, () -> errorID != null), - HttpStatus.ServerError.INTERNAL_SERVER_ERROR.getCode() - ); - } - - /** - * Handles caught {@link HttpStatusException}'s. - * - * @param responder Responds to the {@link Response}. - * @param statusException The caught {@link HttpStatusException}. - * @since 3.7.1 - */ - private void handleHttpStatusException(BiConsumer responder, HttpStatusException statusException) { - responder.accept( - Json.empty() - .set("code", statusException.getCode()) - .set("message", statusException.getMessage()), - statusException.getCode() - ); - } - /** * Handles a http request and determines whether a valid route or share is available * for the given exchange. If a matching route or share is found, the appropriate handler diff --git a/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java b/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java deleted file mode 100644 index 77ef1410..00000000 --- a/src/main/java/de/craftsblock/craftsnet/api/http/status/HttpStatusException.java +++ /dev/null @@ -1,92 +0,0 @@ -package de.craftsblock.craftsnet.api.http.status; - -import org.jetbrains.annotations.NotNull; - -/** - * Exception that represents an HTTP-specific condition. - *

- * This exception encapsulates a {@link HttpStatus} which describes - * the HTTP status code and reason associated with the error. - * It can be used to propagate HTTP-related failures through the - * application stack in a structured way. - *

- * Depending on the constructor used, a custom message and/or cause - * can be provided. If no custom message is specified, the default - * reason phrase of the {@link HttpStatus} will be used. - * - * @author Philipp Maywald - * @author CraftsBlock - * @version 1.0.0 - * @see HttpStatus - * @since 3.7.1 - */ -public class HttpStatusException extends RuntimeException { - - private final HttpStatus status; - - /** - * Creates a new {@link HttpStatusException} using the given status. - *

- * The exception message will default to the reason phrase of the status. - * - * @param status The HTTP status associated with this exception. - */ - public HttpStatusException(@NotNull HttpStatus status) { - this(status, status.getReason()); - } - - /** - * Creates a new {@link HttpStatusException} with a cause. - *

- * The exception message will default to the reason phrase of the status. - * - * @param status The HTTP status associated with this exception. - * @param cause The underlying cause of this exception. - */ - public HttpStatusException(@NotNull HttpStatus status, @NotNull Throwable cause) { - this(status, status.getReason(), cause); - } - - /** - * Creates a new {@link HttpStatusException} with a custom message. - * - * @param status The HTTP status associated with this exception. - * @param message The detail message describing the error. - */ - public HttpStatusException(@NotNull HttpStatus status, @NotNull String message) { - super(message); - this.status = status; - } - - /** - * Creates a new {@link HttpStatusException} with a custom message and cause. - * - * @param status The HTTP status associated with this exception. - * @param message The detail message describing the error. - * @param cause The underlying cause of this exception. - */ - public HttpStatusException(@NotNull HttpStatus status, @NotNull String message, - @NotNull Throwable cause) { - super(message, cause); - this.status = status; - } - - /** - * Returns the {@link HttpStatus} associated with this exception. - * - * @return The HTTP status. - */ - public HttpStatus getStatus() { - return status; - } - - /** - * Returns the numeric HTTP status code. - * - * @return The HTTP status code. - */ - public int getCode() { - return status.getCode(); - } - -} \ No newline at end of file diff --git a/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java b/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java index 62fc4320..2b0b456d 100644 --- a/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java +++ b/src/main/java/de/craftsblock/craftsnet/api/transformers/TransformerPerformer.java @@ -260,7 +260,7 @@ private Object transform(String parameter, Transformer transformer, Class getCallerClass() { * @since 3.3.1-SNAPSHOT */ public static Class getCallerClass(@Range(from = 1, to = Integer.MAX_VALUE) int level) { - return STACK_WALKER.walk(frames -> frames.skip(level) - .findFirst() - .map(StackWalker.StackFrame::getDeclaringClass) - .orElseThrow(() -> new IllegalStateException("No stack frame found for caller level " + level)) - ); + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .walk(frames -> frames.skip(level) + .findFirst() + .map(StackWalker.StackFrame::getDeclaringClass) + .orElseThrow() + ); } /** @@ -88,45 +87,6 @@ public static void restrictToCallers(Class... allowed) { throw new IllegalStateException(callersCaller.getName() + " is not permitted to call a " + caller.getSimpleName()); } - /** - * Rethrows a throwable while unpacking {@link InvocationTargetException}. - * - * @param throwable The {@link InvocationTargetException} that was caught. - * @param The excepted return type used for automatically adjusting to caller method. - * @return Nothing, just for returning in case the calling method needs it. - * @since 3.7.1 - */ - @Contract("_ -> fail") - public static T rethrowReflectionThrowable(Throwable throwable) { - return rethrowReflectionThrowable(throwable, null); - } - - /** - * Rethrows a throwable while unpacking {@link InvocationTargetException}. - * - * @param throwable The {@link InvocationTargetException} that was caught. - * @param alternativeMessage An alternative message for the fallback exception. - * @param The excepted return type used for automatically adjusting to caller method. - * @return Nothing, just for returning in case the calling method needs it. - * @since 3.7.1 - */ - @Contract("_, _ -> fail") - public static T rethrowReflectionThrowable(Throwable throwable, String alternativeMessage) { - if (throwable instanceof InvocationTargetException invocationTargetException) { - return rethrowReflectionThrowable(invocationTargetException, alternativeMessage); - } - - if (throwable instanceof UndeclaredThrowableException undeclaredThrowableException) { - throw undeclaredThrowableException; - } - - if (throwable instanceof RuntimeException runtimeException) { - throw runtimeException; - } - - throw new UndeclaredThrowableException(throwable, alternativeMessage); - } - /** * Checks if a constructor is present in the specified class with the provided argument types. * @@ -187,7 +147,7 @@ public static Constructor findConstructor(Class clazz, Class... arg .unreflectConstructor(constructor) .invokeWithArguments(args); } catch (Throwable e) { - return rethrowReflectionThrowable(e, "Could not create instance of " + type.getSimpleName()); + throw new RuntimeException("Could not create instance of " + type.getSimpleName(), e); } } @@ -205,9 +165,11 @@ public static void setField(String name, Object target, Object value) { .unreflectVarHandle(field) .set(target, value); } catch (Throwable e) { - rethrowReflectionThrowable(e, "Can not set field %s of class %s!".formatted( - name, target.getClass().getSimpleName() - )); + throw new RuntimeException( + "Can not set field %s of class %s!".formatted( + name, target.getClass().getSimpleName() + ), e + ); } } @@ -277,7 +239,9 @@ public static Object invokeMethod(Object owner, Method method, Object... args) { ? handle.bindTo(owner) : handle).invokeWithArguments(args); } catch (Throwable e) { - return rethrowReflectionThrowable(e, "Could not invoke " + method.toGenericString()); + throw new RuntimeException( + "Could not invoke " + method.toGenericString(), e + ); } }